Selaa lähdekoodia

✨ 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.
t0ng7u 3 kuukautta sitten
vanhempi
sitoutus
dc3dba0665

+ 1 - 1
web/src/components/playground/CodeViewer.jsx → web/src/components/common/ui/CodeViewer.jsx

@@ -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, // 最大显示字符数

+ 1 - 1
web/src/components/playground/DebugPanel.jsx

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

+ 23 - 80
web/src/components/settings/oauth2/OAuth2ClientSettings.jsx

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

+ 8 - 8
web/src/components/settings/oauth2/OAuth2ServerSettings.jsx

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

+ 8 - 25
web/src/components/settings/oauth2/modals/JWKSInfoModal.jsx

@@ -18,11 +18,10 @@ For commercial licensing, please contact [email protected]
 */
 
 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>
   );
 };

+ 7 - 27
web/src/components/settings/oauth2/modals/SecretDisplayModal.jsx

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

+ 10 - 25
web/src/components/settings/oauth2/modals/ServerInfoModal.jsx

@@ -18,11 +18,10 @@ For commercial licensing, please contact [email protected]
 */
 
 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>
   );
 };

+ 1 - 1
web/src/components/settings/personal/cards/NotificationSettings.jsx

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

+ 4 - 1
web/src/i18n/locales/en.json

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