diff --git a/src/components/StorageSettings.tsx b/src/components/StorageSettings.tsx index ca75d84..a6c8ec7 100644 --- a/src/components/StorageSettings.tsx +++ b/src/components/StorageSettings.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { CheckCircle2, Cloud, Database, Loader2, LockKeyhole, RadioTower, ShieldCheck } from 'lucide-react'; import type { StorageConfig } from '@shared/types'; import { api, ApiError } from '@/api/client'; @@ -8,10 +8,8 @@ const initialForm = { path_prefix: 'delivery-desk', secret_id: '', secret_key: '', }; -export default function StorageSettings() { - const [items, setItems] = useState([]); +export default function StorageSettings({ items, onItemsChange }: { items: StorageConfig[]; onItemsChange: (items: StorageConfig[]) => void }) { const [form, setForm] = useState(initialForm); - const [loading, setLoading] = useState(true); const [busy, setBusy] = useState<'save' | 'test' | 'activate' | null>(null); const [error, setError] = useState(''); const [notice, setNotice] = useState(''); @@ -19,12 +17,9 @@ export default function StorageSettings() { const draft = useMemo(() => items.find((item) => item.status === 'draft'), [items]); const load = async () => { - setLoading(true); - try { setItems(await api.listStorageConfigs()); } + try { onItemsChange(await api.listStorageConfigs()); } catch (reason) { setError(messageOf(reason)); } - finally { setLoading(false); } }; - useEffect(() => { void load(); }, []); const save = async () => { setBusy('save'); setError(''); setNotice(''); @@ -55,8 +50,6 @@ export default function StorageSettings() { finally { setBusy(null); } }; - if (loading) return
{[1, 2].map((item) =>
)}
; - return

Infrastructure / Object Storage

腾讯云 COS

先保存为草稿,再执行上传、读取和删除测试。只有测试通过的配置可以成为正式存储。

{error &&
{error}
} diff --git a/src/pages/Management.tsx b/src/pages/Management.tsx index 6784526..900adc0 100644 --- a/src/pages/Management.tsx +++ b/src/pages/Management.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { ArrowRightLeft, ChevronDown, Copy, Fingerprint, History, KeyRound, Plus, RefreshCw, Search, ShieldCheck, Users, X } from 'lucide-react'; -import type { AuditLogEntry, ManagedApiKey, ManagedUser, OperationGroup, Project } from '@shared/types'; +import type { AuditLogEntry, ManagedApiKey, ManagedUser, OperationGroup, Project, StorageConfig } from '@shared/types'; import { api, ApiError } from '@/api/client'; import { useAuthStore } from '@/store/useAuthStore'; import StorageSettings from '@/components/StorageSettings'; @@ -28,6 +28,7 @@ export default function ManagementPage() { const [keys, setKeys] = useState([]); const [logs, setLogs] = useState([]); const [projects, setProjects] = useState([]); + const [storageConfigs, setStorageConfigs] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); @@ -53,11 +54,12 @@ export default function ManagementPage() { setLoading(true); setError(''); try { - const [nextAccounts, nextKeys, nextLogs, nextProjects, nextGroups] = await Promise.all([ + const [nextAccounts, nextKeys, nextLogs, nextProjects, nextGroups, nextStorageConfigs] = await Promise.all([ api.listManagedUsers(), api.listApiKeys(), api.listAuditLogs(), api.listProjects(), isPlatform ? api.listGroups() : Promise.resolve([]), + isPlatform ? api.listStorageConfigs() : Promise.resolve([]), ]); - setAccounts(nextAccounts); setKeys(nextKeys); setLogs(nextLogs); setProjects(nextProjects); setGroups(nextGroups); + setAccounts(nextAccounts); setKeys(nextKeys); setLogs(nextLogs); setProjects(nextProjects); setGroups(nextGroups); setStorageConfigs(nextStorageConfigs); } catch (reason) { setError(messageOf(reason)); } finally { setLoading(false); } }, [isPlatform, user]); @@ -67,9 +69,9 @@ export default function ManagementPage() { ...(isPlatform ? [{ id: 'groups' as const, label: '运营组', count: groups.length }] : []), { id: 'accounts' as const, label: '账号', count: accounts.length }, { id: 'keys' as const, label: 'API Key', count: keys.filter((item) => item.status === 'active').length }, - ...(isPlatform ? [{ id: 'storage' as const, label: '对象存储', count: 0 }] : []), + ...(isPlatform ? [{ id: 'storage' as const, label: '对象存储', count: storageConfigs.some((item) => item.status === 'active') ? '已连接' : '未配置' }] : []), { id: 'audit' as const, label: '审计日志', count: logs.length }, - ], [accounts.length, groups.length, isPlatform, keys, logs.length]); + ], [accounts.length, groups.length, isPlatform, keys, logs.length, storageConfigs]); if (user?.role === 'operator') { return

此区域仅对管理员开放

内容账号可以继续管理项目与作品,不具备账号和密钥管理权限。

; @@ -111,7 +113,7 @@ export default function ManagementPage() { {tab === 'groups' && setPanel('group')} onRename={(item)=>{setRenameGroup(item);setRenameGroupValue(item.name);setPanel('rename_group')}} onShowAccounts={(item) => { setAccountGroupFilter(String(item.id)); setTab('accounts'); }} onReplace={(item) => { setReplaceGroup(item); setReplacementUserId(''); setPreviousAdminAction('demote'); setPanel('replace_admin'); }} onToggle={(item) => { if(item.status==='active'&&!window.confirm(`停用“${item.name}”将影响 ${item.active_user_count} 个启用账号、${item.project_count} 个项目和 ${item.customer_link_count} 个客户访问链接。确认继续吗?`))return;void run(() => api.setGroupStatus(item.id, item.status === 'active' ? 'disabled' : 'active').then(() => undefined)); }}/>} {tab === 'accounts' && setPanel('user')} onAudit={(item) => { setAuditUserId(item.id); setTab('audit'); }} onRename={(item) => { setRenameTarget(item); setRenameValue(item.display_name); setPanel('rename'); }} onToggle={(item) => void run(() => api.setUserStatus(item.id, item.status === 'active' ? 'disabled' : 'active').then(() => undefined))} onReset={(item) => { setResetTarget(item); setResetPassword(''); setPanel('reset'); }}/>} {tab === 'keys' && { setRevealedKey(''); setCopyState('idle'); setPanel('key'); }} onRevoke={(item) => void run(() => api.revokeApiKey(item.id))}/>} - {tab === 'storage' && isPlatform && } + {tab === 'storage' && isPlatform && } {tab === 'audit' && item.id===auditUserId) : undefined} onClear={() => setAuditUserId(null)}/>} }