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:
t0ng7u
2025-09-20 23:19:42 +08:00
parent 81272da9ac
commit dc3dba0665
9 changed files with 63 additions and 169 deletions

View File

@@ -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, //

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>
}

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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';

View File

@@ -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"
}