import { useCallback, useEffect, useMemo, useState } from 'react' import { Check, ChevronDown, ChevronUp, Copy, Edit3, Eye, Key, Plus, RefreshCw, ShieldCheck, Trash2, X, } from 'lucide-react' import api, { APIResponse, Container } from '../services/api' import { useLanguage } from '../contexts/LanguageContext' import { copyToClipboard } from '../utils/clipboard' interface ApiKeyItem { id: string name: string key?: string prefix: string ip_whitelist: string created_at: string last_used: string scopes?: string[] expires_at?: string disabled?: boolean container_uuids?: string[] last_used_ip?: string } interface ApiKeyForm { name: string ipWhitelist: string scopes: string[] expiresAt: string disabled: boolean containerUUIDs: string[] } const BASE_URL = window.location.origin const SAMPLE_BASE_URL = 'https://panel.example.com' type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' type EndpointTuple = [HttpMethod, string, string] interface EndpointDoc { method: HttpMethod path: string desc: string examplePath: string body?: Record response: unknown note?: string } const scopeGroups = [ { title: '总览与只读', scopes: [ ['dashboard:read', '控制面板'], ['host:read', '主机资源'], ['routing:read', '路由信息'], ['routing:write', '路由配置'], ['ipv6:read', 'IPv6 状态'], ['task:read', '任务列表'], ['task:delete', '删除任务'], ['image:read', '镜像列表'], ], }, { title: '容器', scopes: [ ['container:read', '查看容器'], ['container:create', '创建容器'], ['container:power', '开关机/重启'], ['container:reinstall', '重装系统'], ['container:delete', '删除容器'], ['container:resize', '资源/到期'], ['container:traffic', '流量管理'], ['container:network', '端口映射'], ['container:password', '重置密码'], ['ipv6:assign', '分配 IPv6'], ], }, { title: '快照与终端', scopes: [ ['snapshot:read', '查看快照'], ['snapshot:create', '创建快照'], ['snapshot:delete', '删除快照'], ['snapshot:restore', '恢复快照'], ['snapshot:schedule', '计划/配额'], ['terminal:ssh', 'WebSSH 票据'], ['terminal:vnc', 'WebVNC 票据'], ], }, { title: '平台管理', scopes: [ ['image:download', '下载镜像'], ['image:delete', '删除镜像'], ['image:toggle', '启停镜像'], ['security:read', '安全数据'], ['security:check', '安全扫描'], ['security:settings', '安全设置'], ['swap:read', 'Swap 信息'], ['swap:manage', 'Swap 管理'], ['subuser:read', '子用户列表'], ['subuser:create', '创建子用户'], ['subuser:update', '更新子用户'], ['audit:read', '操作日志'], ['loginlog:read', '登录日志'], ['apikey:read', 'Key 列表'], ['apikey:create', '创建 Key'], ['apikey:update', '更新 Key'], ['apikey:delete', '删除 Key'], ['admin:access', '管理员接口'], ], }, ] const defaultReadScopes = [ 'dashboard:read', 'container:read', 'task:read', 'image:read', 'snapshot:read', 'routing:read', 'ipv6:read', 'host:read', ] const endpointGroups: Array<{ title: string; endpoints: EndpointTuple[] }> = [ { title: '总览', endpoints: [ ['GET', '/api/v1/dashboard', '控制面板统计'], ['GET', '/api/v1/host-info', '主机资源'], ['GET', '/api/v1/routing', 'NAT/IPv4/IPv6 路由'], ['PUT', '/api/v1/routing', '更新公网 IPv4/IPv6 池'], ['POST', '/api/v1/routing/ipv4-scan', '扫描公网 IPv4 段'], ['GET', '/api/v1/ipv6/status', 'IPv6 状态'], ['GET', '/api/v1/tasks', '任务队列'], ['DELETE', '/api/v1/tasks/{task_id}', '删除任务'], ], }, { title: '容器', endpoints: [ ['GET', '/api/v1/containers', '容器列表'], ['POST', '/api/v1/containers/list', '容器列表(兼容 POST 写法)'], ['POST', '/api/v1/containers', '创建容器'], ['GET', '/api/v1/containers/{id|uuid|name}', '容器详情'], ['POST', '/api/v1/containers/{id}/start', '开机'], ['POST', '/api/v1/containers/{id}/stop', '关机'], ['POST', '/api/v1/containers/{id}/restart', '重启'], ['POST', '/api/v1/containers/{id}/reinstall', '重装'], ['DELETE', '/api/v1/containers/{id}/delete', '删除'], ['GET', '/api/v1/containers/{id}/usage', '资源用量'], ['GET', '/api/v1/containers/{id}/traffic', '流量统计'], ['POST', '/api/v1/containers/{id}/traffic-reset', '重置流量'], ['PUT', '/api/v1/containers/{id}/traffic-limit', '调整流量限制'], ['PUT', '/api/v1/containers/{id}/resource-limit', '调整资源限制'], ['PUT', '/api/v1/containers/{id}/expiry', '调整到期时间'], ['POST', '/api/v1/containers/{id}/reset-password', '重置 SSH 密码'], ['POST', '/api/v1/containers/{id}/ipv6', '分配 IPv6'], ], }, { title: '端口与快照', endpoints: [ ['GET', '/api/v1/containers/{id}/random-port', '随机可用端口'], ['POST', '/api/v1/containers/{id}/port-mappings', '添加端口映射'], ['PUT', '/api/v1/containers/{id}/port-mappings/{index}', '更新端口映射'], ['DELETE', '/api/v1/containers/{id}/port-mappings/{index}', '删除端口映射'], ['GET', '/api/v1/containers/{id}/firewall', '获取防火墙设置'], ['PUT', '/api/v1/containers/{id}/firewall', '更新防火墙设置'], ['GET', '/api/v1/snapshots', '快照总览'], ['GET', '/api/v1/containers/{id}/snapshots', '容器快照'], ['POST', '/api/v1/containers/{id}/snapshots', '创建快照'], ['DELETE', '/api/v1/containers/{id}/snapshots/{snapshot_id}', '删除快照'], ['POST', '/api/v1/containers/{id}/snapshots/{snapshot_id}/restore', '恢复快照'], ['POST', '/api/v1/containers/{id}/snapshots/schedule', '计划快照'], ['PUT', '/api/v1/containers/{id}/snapshots/quota', '快照配额'], ], }, { title: '平台管理', endpoints: [ ['GET', '/api/v1/templates', '模板列表'], ['GET', '/api/v1/images', '镜像管理列表'], ['POST', '/api/v1/images/download', '下载镜像'], ['POST', '/api/v1/images/cancel', '取消镜像下载'], ['DELETE', '/api/v1/images/delete', '删除镜像缓存'], ['PUT', '/api/v1/images/toggle', '启用/禁用镜像'], ['GET', '/api/v1/security/alerts', '安全告警'], ['POST', '/api/v1/security/check', '立即安全检查'], ['GET', '/api/v1/security/logs?container={name}', '安全连接日志'], ['GET', '/api/v1/security/summary', '安全汇总'], ['GET', '/api/v1/security/settings', '安全设置'], ['PUT', '/api/v1/security/settings', '更新安全设置'], ['GET', '/api/v1/swap', 'Swap 信息'], ['POST', '/api/v1/swap', '调整 Swap'], ['POST', '/api/v1/batch-create', '批量创建容器'], ['POST', '/api/v1/batch-action', '批量开关机/删除/重装'], ['POST', '/api/v1/ssh-ticket', '创建 WebSSH 票据'], ['POST', '/api/v1/vnc-ticket', '创建 WebVNC 票据'], ], }, { title: '账号与日志', endpoints: [ ['POST', '/api/v1/sub-user/create', '创建子用户链接'], ['GET', '/api/v1/sub-users', '子用户列表'], ['POST', '/api/v1/sub-users/{id}/rotate-password', '轮换子用户密码'], ['GET', '/api/v1/sub-users/{id}/audit-logs', '子用户操作日志'], ['GET', '/api/v1/sub-users/{id}/login-logs', '子用户登录日志'], ['GET', '/api/v1/audit-logs', '操作日志'], ['GET', '/api/v1/login-logs', '登录日志'], ['GET', '/api/v1/api-keys', 'API Key 列表'], ['POST', '/api/v1/api-keys', '创建 API Key'], ['PATCH', '/api/v1/api-keys/{id}', '更新 API Key'], ['DELETE', '/api/v1/api-keys/{id}', '删除 API Key'], ], }, ] const emptyForm = (): ApiKeyForm => ({ name: '', ipWhitelist: '', scopes: [...defaultReadScopes], expiresAt: '', disabled: false, containerUUIDs: [], }) export default function ApiIntegration() { const { t } = useLanguage() const [keys, setKeys] = useState([]) const [containers, setContainers] = useState([]) const [loading, setLoading] = useState(true) const [showForm, setShowForm] = useState(false) const [editingKey, setEditingKey] = useState(null) const [form, setForm] = useState(emptyForm) const [saving, setSaving] = useState(false) const [newKey, setNewKey] = useState('') const [copiedKey, setCopiedKey] = useState(false) const [showDocs, setShowDocs] = useState(true) const [selectedEndpoint, setSelectedEndpoint] = useState(null) const [copiedDoc, setCopiedDoc] = useState(false) const containerNameByUUID = useMemo(() => { const map = new Map() containers.forEach(c => map.set(c.uuid, c.name)) return map }, [containers]) const fetchData = useCallback(async () => { setLoading(true) try { const [keyRes, containerRes] = await Promise.all([ api.get>('/api-keys'), api.get>('/containers'), ]) setKeys(keyRes.data.data || []) setContainers(containerRes.data.data || []) } catch { // keep the page usable if one request fails } finally { setLoading(false) } }, []) useEffect(() => { fetchData() }, [fetchData]) const openCreate = () => { setEditingKey(null) setForm(emptyForm()) setShowForm(true) } const openEdit = (item: ApiKeyItem) => { setEditingKey(item) setForm({ name: item.name, ipWhitelist: item.ip_whitelist || '', scopes: item.scopes?.length ? item.scopes : ['*'], expiresAt: toDateTimeLocal(item.expires_at || ''), disabled: Boolean(item.disabled), containerUUIDs: item.container_uuids || [], }) setShowForm(true) } const saveKey = async () => { if (!form.name.trim()) return setSaving(true) const payload = { name: form.name.trim(), ip_whitelist: form.ipWhitelist.trim(), scopes: form.scopes, expires_at: fromDateTimeLocal(form.expiresAt), disabled: form.disabled, container_uuids: form.containerUUIDs, } try { if (editingKey) { const res = await api.patch>(`/api-keys/${editingKey.id}`, payload) if (res.data.data) { setKeys(prev => prev.map(k => (k.id === editingKey.id ? res.data.data! : k))) } } else { const res = await api.post>('/api-keys', payload) if (res.data.data) { setKeys(prev => [res.data.data!, ...prev]) if (res.data.data.key) setNewKey(res.data.data.key) } } setShowForm(false) } catch { // axios interceptor handles auth; form stays open } finally { setSaving(false) } } const deleteKey = async (id: string) => { if (!window.confirm(t('确定删除这个 API Key 吗?'))) return try { await api.delete(`/api-keys/${id}`) setKeys(prev => prev.filter(k => k.id !== id)) } catch { // ignore } } const copyKey = async () => { const copied = await copyToClipboard(newKey) if (copied) { setCopiedKey(true) setTimeout(() => setCopiedKey(false), 1600) } } const copyDocCode = async () => { if (!selectedEndpoint) return const copied = await copyToClipboard(buildPythonExample(selectedEndpoint)) if (copied) { setCopiedDoc(true) setTimeout(() => setCopiedDoc(false), 1600) } } const toggleScope = (scope: string) => { setForm(prev => { if (scope === '*') { return { ...prev, scopes: prev.scopes.includes('*') ? [...defaultReadScopes] : ['*'] } } const withoutAll = prev.scopes.filter(s => s !== '*') const scopes = withoutAll.includes(scope) ? withoutAll.filter(s => s !== scope) : [...withoutAll, scope] return { ...prev, scopes: scopes.length ? scopes : [...defaultReadScopes] } }) } const toggleContainer = (uuid: string) => { setForm(prev => ({ ...prev, containerUUIDs: prev.containerUUIDs.includes(uuid) ? prev.containerUUIDs.filter(item => item !== uuid) : [...prev.containerUUIDs, uuid], })) } return (

API 集成

管理外部调用凭据、权限范围与平台 API 文档

{newKey && (
新的 API Key 已生成
{newKey}
)}

API Keys

{loading ? (
加载中...
) : keys.length === 0 ? (
暂无 API Key
) : (
{keys.map(item => ( ))}
名称 权限 绑定容器 限制 最后使用 操作
{item.name} {item.disabled && ( 已禁用 )}
{item.prefix}
{item.container_uuids?.length ? item.container_uuids.map(uuid => containerNameByUUID.get(uuid) || uuid).join('、') : '全部容器'}
{item.ip_whitelist ? 'IP 白名单' : '不限 IP'}
{item.expires_at ? `到期 ${item.expires_at}` : '长期有效'}
{item.last_used || '从未使用'}
{item.last_used_ip &&
{item.last_used_ip}
}
)}
{showDocs && (
curl -X GET {BASE_URL}/api/v1/containers -H "X-API-Key: clicd_sk_xxxx"
curl -X GET {BASE_URL}/api/v1/dashboard -H "Authorization: Bearer clicd_sk_xxxx"
{endpointGroups.map(group => (

{group.title}

{group.endpoints.map(([method, path, desc]) => (
{method} {path} {desc}
))}
))}
)}
{selectedEndpoint && (
setSelectedEndpoint(null)} />
{selectedEndpoint.method} {selectedEndpoint.path}

{selectedEndpoint.desc}

Python 使用范例

                    {buildPythonExample(selectedEndpoint)}
                  

返回响应样例

{selectedEndpoint.note && (
{selectedEndpoint.note}
)}
                    {formatJSON(selectedEndpoint.response)}
                  
)} {showForm && (
setShowForm(false)} />

{editingKey ? '编辑 API Key' : '创建 API Key'}