mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-06-07 22:09:57 +00:00
✨ enhance(oauth2): improve UI components and code display experience
- **Table Layout Optimization:**
- Remove description column from OAuth2 client table to save space
- Add tooltip on client name hover to display full description
- Adjust table scroll width from 1200px to 1000px for better layout
- Improve client name column width to 180px for better readability
- **Action Button Simplification:**
- Replace icon-only buttons with text labels for better accessibility
- Simplify Popconfirm content by removing complex styled layouts
- Remove unnecessary Tooltip wrappers around action buttons
- Clean up unused Lucide icon imports (Edit, Key, Trash2)
- **Code Display Enhancement:**
- Replace basic <pre> tags with CodeViewer component in modal dialogs
- Add syntax highlighting for JSON content in ServerInfoModal and JWKSInfoModal
- Implement copy-to-clipboard functionality for server info and JWKS data
- Add performance optimization for large content display
- Provide expandable/collapsible interface for better UX
- **Component Architecture:**
- Import and integrate CodeViewer component in both modal components
- Set appropriate props: content, title, and language='json'
- Maintain loading states and error handling functionality
- **Internationalization:**
- Add English translations for new UI elements:
* '暂无描述': 'No description'
* 'OAuth2 服务器配置': 'OAuth2 Server Configuration'
* 'JWKS 密钥集': 'JWKS Key Set'
- **User Experience Improvements:**
- Enhanced tooltip interaction for description display
- Better visual feedback with cursor-help styling
- Improved code readability with professional dark theme
- Consistent styling across all OAuth2 management interfaces
This update focuses on UI/UX improvements while maintaining full functionality and adding modern code viewing capabilities to the OAuth2 management system.
This commit is contained in:
@@ -21,7 +21,7 @@ import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { Button, Tooltip, Toast } from '@douyinfe/semi-ui';
|
||||
import { Copy, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { copy } from '../../helpers';
|
||||
import { copy } from '../../../helpers';
|
||||
|
||||
const PERFORMANCE_CONFIG = {
|
||||
MAX_DISPLAY_LENGTH: 50000, // 最大显示字符数
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { Code, Zap, Clock, X, Eye, Send } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CodeViewer from './CodeViewer';
|
||||
import CodeViewer from '../common/ui/CodeViewer';
|
||||
|
||||
const DebugPanel = ({
|
||||
debugData,
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
Tooltip,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconSearch } from '@douyinfe/semi-icons';
|
||||
import { User, Grid3X3 } from 'lucide-react';
|
||||
import { User } from 'lucide-react';
|
||||
import { API, showError, showSuccess } from '../../../helpers';
|
||||
import CreateOAuth2ClientModal from './modals/CreateOAuth2ClientModal';
|
||||
import EditOAuth2ClientModal from './modals/EditOAuth2ClientModal';
|
||||
@@ -138,13 +138,14 @@ export default function OAuth2ClientSettings() {
|
||||
{
|
||||
title: t('客户端名称'),
|
||||
dataIndex: 'name',
|
||||
render: (name) => (
|
||||
<div className='flex items-center'>
|
||||
render: (name, record) => (
|
||||
<div className='flex items-center cursor-help'>
|
||||
<User size={16} className='mr-1.5 text-gray-500' />
|
||||
<Text strong>{name}</Text>
|
||||
<Tooltip content={record.description || t('暂无描述')} position='top'>
|
||||
<Text strong>{name}</Text>
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('客户端ID'),
|
||||
@@ -154,30 +155,24 @@ export default function OAuth2ClientSettings() {
|
||||
{id}
|
||||
</Text>
|
||||
),
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('描述'),
|
||||
dataIndex: 'description',
|
||||
render: (description) => (
|
||||
<Text type='tertiary' size='small'>
|
||||
{description || '-'}
|
||||
</Text>
|
||||
title: t('状态'),
|
||||
dataIndex: 'status',
|
||||
render: (status) => (
|
||||
<Tag color={status === 1 ? 'green' : 'red'} shape='circle'>
|
||||
{status === 1 ? t('启用') : t('禁用')}
|
||||
</Tag>
|
||||
),
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('类型'),
|
||||
dataIndex: 'client_type',
|
||||
render: (text) => (
|
||||
<Tag
|
||||
color={text === 'confidential' ? 'blue' : 'green'}
|
||||
style={{ borderRadius: '12px' }}
|
||||
>
|
||||
<Tag color='white' shape='circle'>
|
||||
{text === 'confidential' ? t('机密客户端') : t('公开客户端')}
|
||||
</Tag>
|
||||
),
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('授权类型'),
|
||||
@@ -195,7 +190,7 @@ export default function OAuth2ClientSettings() {
|
||||
return (
|
||||
<div className='flex flex-wrap gap-1'>
|
||||
{types.slice(0, 2).map((type) => (
|
||||
<Tag key={type} size='small' style={{ borderRadius: '8px' }}>
|
||||
<Tag color='white' key={type} size='small' shape='circle'>
|
||||
{typeMap[type] || type}
|
||||
</Tag>
|
||||
))}
|
||||
@@ -206,7 +201,7 @@ export default function OAuth2ClientSettings() {
|
||||
.map((t) => typeMap[t] || t)
|
||||
.join(', ')}
|
||||
>
|
||||
<Tag size='small' style={{ borderRadius: '8px' }}>
|
||||
<Tag color='white' size='small' shape='circle'>
|
||||
+{types.length - 2}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
@@ -214,106 +209,54 @@ export default function OAuth2ClientSettings() {
|
||||
</div>
|
||||
);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('状态'),
|
||||
dataIndex: 'status',
|
||||
render: (status) => (
|
||||
<Tag
|
||||
color={status === 1 ? 'green' : 'red'}
|
||||
style={{ borderRadius: '12px' }}
|
||||
>
|
||||
{status === 1 ? t('启用') : t('禁用')}
|
||||
</Tag>
|
||||
),
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('创建时间'),
|
||||
dataIndex: 'created_time',
|
||||
render: (time) => new Date(time * 1000).toLocaleString(),
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
render: (_, record) => (
|
||||
<Space size={4} wrap>
|
||||
<Button
|
||||
theme='borderless'
|
||||
type='primary'
|
||||
size='small'
|
||||
onClick={() => {
|
||||
setEditingClient(record);
|
||||
setShowEditModal(true);
|
||||
}}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
{t('编辑')}
|
||||
</Button>
|
||||
{record.client_type === 'confidential' && (
|
||||
<Popconfirm
|
||||
title={t('确认重新生成客户端密钥?')}
|
||||
content={
|
||||
<div style={{ maxWidth: 280 }}>
|
||||
<div className='mb-2'>
|
||||
<Text strong>{t('客户端')}:</Text>
|
||||
<Text>{record.name}</Text>
|
||||
</div>
|
||||
<div className='p-3 bg-orange-50 border border-orange-200 rounded-md'>
|
||||
<Text size='small' type='warning'>
|
||||
⚠️ {t('操作不可撤销,旧密钥将立即失效。')}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
content={t('操作不可撤销,旧密钥将立即失效。')}
|
||||
onConfirm={() => handleRegenerateSecret(record)}
|
||||
okText={t('确认')}
|
||||
cancelText={t('取消')}
|
||||
position='bottomLeft'
|
||||
>
|
||||
<Button
|
||||
theme='borderless'
|
||||
type='secondary'
|
||||
size='small'
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
<Button type='secondary' size='small'>
|
||||
{t('重新生成密钥')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
<Popconfirm
|
||||
title={t('请再次确认删除该客户端')}
|
||||
content={
|
||||
<div style={{ maxWidth: 280 }}>
|
||||
<div className='mb-2'>
|
||||
<Text strong>{t('客户端')}:</Text>
|
||||
<Text>{record.name}</Text>
|
||||
</div>
|
||||
<div className='p-3 bg-red-50 border border-red-200 rounded-md'>
|
||||
<Text size='small' type='danger'>
|
||||
🗑️ {t('删除后无法恢复,相关 API 调用将立即失效。')}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
content={t('删除后无法恢复,相关 API 调用将立即失效。')}
|
||||
onConfirm={() => handleDelete(record)}
|
||||
okText={t('确定删除')}
|
||||
cancelText={t('取消')}
|
||||
position='bottomLeft'
|
||||
>
|
||||
<Button
|
||||
theme='borderless'
|
||||
type='danger'
|
||||
size='small'
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
<Button type='danger' size='small'>
|
||||
{t('删除')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
},
|
||||
];
|
||||
@@ -348,13 +291,13 @@ export default function OAuth2ClientSettings() {
|
||||
size='small'
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
<Button onClick={loadClients} size='small'>
|
||||
<Button type='tertiary' onClick={loadClients} size='small'>
|
||||
{t('刷新')}
|
||||
</Button>
|
||||
<Button onClick={showServerInfo} size='small'>
|
||||
<Button type='secondary' onClick={showServerInfo} size='small'>
|
||||
{t('服务器信息')}
|
||||
</Button>
|
||||
<Button onClick={showJWKS} size='small'>
|
||||
<Button type='secondary' onClick={showJWKS} size='small'>
|
||||
{t('查看JWKS')}
|
||||
</Button>
|
||||
<Button
|
||||
@@ -382,7 +325,7 @@ export default function OAuth2ClientSettings() {
|
||||
dataSource={filteredClients}
|
||||
rowKey='id'
|
||||
loading={loading}
|
||||
scroll={{ x: 1200 }}
|
||||
scroll={{ x: 'max-content' }}
|
||||
style={{ marginTop: 8 }}
|
||||
pagination={{
|
||||
showSizeChanger: true,
|
||||
|
||||
@@ -244,14 +244,6 @@ export default function OAuth2ServerSettings(props) {
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center gap-2 sm:flex-shrink-0'>
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={onSubmit}
|
||||
loading={loading}
|
||||
size='small'
|
||||
>
|
||||
{t('保存配置')}
|
||||
</Button>
|
||||
{isEnabled && (
|
||||
<Button
|
||||
type='secondary'
|
||||
@@ -261,6 +253,14 @@ export default function OAuth2ServerSettings(props) {
|
||||
{t('密钥管理')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={onSubmit}
|
||||
loading={loading}
|
||||
size='small'
|
||||
>
|
||||
{t('保存配置')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Typography } from '@douyinfe/semi-ui';
|
||||
import { Modal } from '@douyinfe/semi-ui';
|
||||
import { API, showError } from '../../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const { Text } = Typography;
|
||||
import CodeViewer from '../../../common/ui/CodeViewer';
|
||||
|
||||
const JWKSInfoModal = ({ visible, onClose }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -49,14 +48,7 @@ const JWKSInfoModal = ({ visible, onClose }) => {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
<span>🔐</span>
|
||||
<Text strong className='ml-2'>
|
||||
{t('JWKS 信息')}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
title={t('JWKS 信息')}
|
||||
visible={visible}
|
||||
onCancel={onClose}
|
||||
onOk={onClose}
|
||||
@@ -66,20 +58,11 @@ const JWKSInfoModal = ({ visible, onClose }) => {
|
||||
bodyStyle={{ padding: '20px 24px' }}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<pre
|
||||
style={{
|
||||
background: 'var(--semi-color-fill-0)',
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
fontSize: '12px',
|
||||
maxHeight: '400px',
|
||||
overflow: 'auto',
|
||||
border: '1px solid var(--semi-color-border)',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{jwksInfo ? JSON.stringify(jwksInfo, null, 2) : t('加载中...')}
|
||||
</pre>
|
||||
<CodeViewer
|
||||
content={jwksInfo ? JSON.stringify(jwksInfo, null, 2) : t('加载中...')}
|
||||
title={t('JWKS 密钥集')}
|
||||
language='json'
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,14 +28,7 @@ const SecretDisplayModal = ({ visible, onClose, secret }) => {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
<span>🔑</span>
|
||||
<Text strong className='ml-2'>
|
||||
{t('客户端密钥已重新生成')}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
title={t('客户端密钥已重新生成')}
|
||||
visible={visible}
|
||||
onCancel={onClose}
|
||||
onOk={onClose}
|
||||
@@ -45,29 +38,16 @@ const SecretDisplayModal = ({ visible, onClose, secret }) => {
|
||||
bodyStyle={{ padding: '20px 24px' }}
|
||||
>
|
||||
<Banner
|
||||
type='warning'
|
||||
type='success'
|
||||
closeIcon={null}
|
||||
description={t(
|
||||
'新的客户端密钥如下,请立即复制保存。关闭此窗口后将无法再次查看。',
|
||||
)}
|
||||
className='mb-5'
|
||||
className='mb-5 !rounded-lg'
|
||||
/>
|
||||
<div className='bg-gray-50 p-4 rounded-lg border font-mono break-all'>
|
||||
<Text
|
||||
code
|
||||
copyable={{
|
||||
content: secret,
|
||||
successTip: t('已复制到剪贴板'),
|
||||
}}
|
||||
style={{ fontSize: '13px', lineHeight: '1.5' }}
|
||||
>
|
||||
{secret}
|
||||
</Text>
|
||||
</div>
|
||||
<div className='mt-3 p-3 bg-blue-50 border border-blue-200 rounded-md'>
|
||||
<Text size='small' type='tertiary'>
|
||||
💡 {t('请妥善保管此密钥,用于应用程序的身份验证')}
|
||||
</Text>
|
||||
</div>
|
||||
<Text code copyable>
|
||||
{secret}
|
||||
</Text>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,11 +18,10 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Typography } from '@douyinfe/semi-ui';
|
||||
import { Modal } from '@douyinfe/semi-ui';
|
||||
import { API, showError } from '../../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const { Text } = Typography;
|
||||
import CodeViewer from '../../../common/ui/CodeViewer';
|
||||
|
||||
const ServerInfoModal = ({ visible, onClose }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -49,14 +48,7 @@ const ServerInfoModal = ({ visible, onClose }) => {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
<span>🖥️</span>
|
||||
<Text strong className='ml-2'>
|
||||
{t('OAuth2 服务器信息')}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
title={t('OAuth2 服务器信息')}
|
||||
visible={visible}
|
||||
onCancel={onClose}
|
||||
onOk={onClose}
|
||||
@@ -66,20 +58,13 @@ const ServerInfoModal = ({ visible, onClose }) => {
|
||||
bodyStyle={{ padding: '20px 24px' }}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<pre
|
||||
style={{
|
||||
background: 'var(--semi-color-fill-0)',
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
fontSize: '12px',
|
||||
maxHeight: '400px',
|
||||
overflow: 'auto',
|
||||
border: '1px solid var(--semi-color-border)',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{serverInfo ? JSON.stringify(serverInfo, null, 2) : t('加载中...')}
|
||||
</pre>
|
||||
<CodeViewer
|
||||
content={
|
||||
serverInfo ? JSON.stringify(serverInfo, null, 2) : t('加载中...')
|
||||
}
|
||||
title={t('OAuth2 服务器配置')}
|
||||
language='json'
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
showSuccess,
|
||||
showError,
|
||||
} from '../../../../helpers';
|
||||
import CodeViewer from '../../../playground/CodeViewer';
|
||||
import CodeViewer from '../../../common/ui/CodeViewer';
|
||||
import { StatusContext } from '../../../../context/Status';
|
||||
import { UserContext } from '../../../../context/User';
|
||||
import { useUserPermissions } from '../../../../hooks/common/useUserPermissions';
|
||||
|
||||
@@ -2174,5 +2174,8 @@
|
||||
"生产环境务必启用 HTTPS,并妥善管理 JWT 签名密钥": "Enable HTTPS in production and manage JWT signing keys properly",
|
||||
"个客户端": "clients",
|
||||
"请妥善保管此密钥,用于应用程序的身份验证": "Please keep this secret safe, it is used for application authentication",
|
||||
"客户端名称": "Client Name"
|
||||
"客户端名称": "Client Name",
|
||||
"暂无描述": "No description",
|
||||
"OAuth2 服务器配置": "OAuth2 Server Configuration",
|
||||
"JWKS 密钥集": "JWKS Key Set"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user