@@ -58,10 +58,12 @@ import {
5858 faTable ,
5959 faFileLines ,
6060 faLayerGroup ,
61- faSync
61+ faSync ,
62+ faKey
6263} from "@fortawesome/free-solid-svg-icons" ;
6364import AddEndpointModal from "./components/add-endpoint-modal" ;
6465import RenameEndpointModal from "./components/rename-endpoint-modal" ;
66+ import EditApiKeyModal from "./components/edit-apikey-modal" ;
6567import { buildApiUrl } from '@/lib/utils' ;
6668import { copyToClipboard } from '@/lib/utils/clipboard' ;
6769import ManualCopyModal from '@/components/ui/manual-copy-modal' ;
@@ -115,6 +117,7 @@ export default function EndpointsPage() {
115117 const { isOpen : isAddOpen , onOpen : onAddOpen , onOpenChange : onAddOpenChange } = useDisclosure ( ) ;
116118 const { isOpen : isDeleteOpen , onOpen : onDeleteOpen , onOpenChange : onDeleteOpenChange } = useDisclosure ( ) ;
117119 const { isOpen : isRenameOpen , onOpen : onRenameOpen , onOpenChange : onRenameOpenChange } = useDisclosure ( ) ;
120+ const { isOpen : isEditApiKeyOpen , onOpen : onEditApiKeyOpen , onOpenChange : onEditApiKeyOpenChange } = useDisclosure ( ) ;
118121 const [ selectedEndpoint , setSelectedEndpoint ] = useState < FormattedEndpoint | null > ( null ) ;
119122 // Next.js 路由
120123 const router = useRouter ( ) ;
@@ -509,6 +512,9 @@ export default function EndpointsPage() {
509512 case 'rename' :
510513 handleCardClick ( endpoint ) ;
511514 break ;
515+ case 'editApiKey' :
516+ handleEditApiKeyClick ( endpoint ) ;
517+ break ;
512518 case 'copy' :
513519 handleCopyConfig ( endpoint ) ;
514520 break ;
@@ -525,6 +531,7 @@ export default function EndpointsPage() {
525531 < DropdownItem key = "addTunnel" startContent = { < FontAwesomeIcon icon = { faPlus } /> } className = "text-primary" color = "primary" > 添加实例</ DropdownItem >
526532 < DropdownItem key = "refresTunnel" startContent = { < FontAwesomeIcon icon = { faSync } /> } className = "text-secondary" color = "secondary" > 同步实例</ DropdownItem >
527533 < DropdownItem key = "rename" startContent = { < FontAwesomeIcon icon = { faPen } /> } className = "text-warning" color = "warning" > 重命名</ DropdownItem >
534+ < DropdownItem key = "editApiKey" startContent = { < FontAwesomeIcon icon = { faKey } /> } className = "text-warning" color = "warning" > 修改密钥</ DropdownItem >
528535 < DropdownItem key = "copy" startContent = { < FontAwesomeIcon icon = { faCopy } /> } className = "text-success" color = "success" > 复制配置</ DropdownItem >
529536 < DropdownItem
530537 key = "toggle"
@@ -618,6 +625,61 @@ export default function EndpointsPage() {
618625 }
619626 } ;
620627
628+ // 处理修改密钥
629+ const handleEditApiKey = async ( newApiKey : string ) => {
630+ if ( ! selectedEndpoint ?. id ) return ;
631+
632+ try {
633+ // 1. 先断开连接
634+ await handleDisconnect ( selectedEndpoint . id ) ;
635+
636+ // 2. 更新密钥
637+ const response = await fetch ( buildApiUrl ( `/api/endpoints/${ selectedEndpoint . id } ` ) , {
638+ method : 'PUT' ,
639+ headers : {
640+ 'Content-Type' : 'application/json' ,
641+ } ,
642+ body : JSON . stringify ( {
643+ apiKey : newApiKey ,
644+ action : 'editApiKey'
645+ } ) ,
646+ } ) ;
647+
648+ if ( ! response . ok ) {
649+ const errorData = await response . json ( ) ;
650+ throw new Error ( errorData . error || '修改密钥失败' ) ;
651+ }
652+
653+ addToast ( {
654+ title : "密钥修改成功" ,
655+ description : "API Key 已更新,正在重新连接..." ,
656+ color : "success" ,
657+ } ) ;
658+
659+ // 3. 刷新主控列表
660+ await fetchEndpoints ( ) ;
661+
662+ // 4. 重新连接
663+ setTimeout ( async ( ) => {
664+ await handleConnect ( selectedEndpoint . id ) ;
665+ } , 1000 ) ;
666+
667+ } catch ( error ) {
668+ addToast ( {
669+ title : "修改密钥失败" ,
670+ description : error instanceof Error ? error . message : "请稍后重试" ,
671+ color : "danger" ,
672+ } ) ;
673+ throw error ; // 重新抛出错误以便模态框处理
674+ }
675+ } ;
676+
677+ // 打开修改密钥弹窗
678+ const handleEditApiKeyClick = ( endpoint : FormattedEndpoint ) => {
679+ setSelectedEndpoint ( endpoint ) ;
680+ onEditApiKeyOpen ( ) ;
681+ } ;
682+
621683 // 打开添加隧道弹窗
622684 const { isOpen : isAddTunnelOpen , onOpen : onAddTunnelOpen , onOpenChange : onAddTunnelOpenChange } = useDisclosure ( ) ;
623685 const [ tunnelUrl , setTunnelUrl ] = useState ( '' ) ;
@@ -1014,6 +1076,12 @@ export default function EndpointsPage() {
10141076 < FontAwesomeIcon icon = { faSync } />
10151077 </ Button >
10161078 </ Tooltip >
1079+ { /* 修改密钥 */ }
1080+ < Tooltip content = "修改密钥" >
1081+ < Button isIconOnly size = "sm" variant = "light" color = "warning" onPress = { ( ) => handleEditApiKeyClick ( ep ) } >
1082+ < FontAwesomeIcon icon = { faKey } />
1083+ </ Button >
1084+ </ Tooltip >
10171085 { /* 复制配置 */ }
10181086 < Tooltip content = "复制配置" >
10191087 < Button isIconOnly size = "sm" variant = "light" color = "success" onPress = { ( ) => handleCopyConfig ( ep ) } >
@@ -1074,6 +1142,17 @@ export default function EndpointsPage() {
10741142 />
10751143 ) }
10761144
1145+ { /* 修改密钥模态框 */ }
1146+ { selectedEndpoint && (
1147+ < EditApiKeyModal
1148+ isOpen = { isEditApiKeyOpen }
1149+ onOpenChange = { onEditApiKeyOpenChange }
1150+ currentApiKey = { selectedEndpoint . apiKey }
1151+ endpointName = { selectedEndpoint . name }
1152+ onSave = { handleEditApiKey }
1153+ />
1154+ ) }
1155+
10771156 { /* 添加隧道弹窗 */ }
10781157 < Modal isOpen = { isAddTunnelOpen } onOpenChange = { onAddTunnelOpenChange } placement = "center" >
10791158 < ModalContent >
0 commit comments