From e1fd098a9727268d2abbd00432aeb75f3ea59d4f Mon Sep 17 00:00:00 2001 From: aiyimickey <39365912+aiyimickey@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:35:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20monitor=20permissions=20sync=20+=20univ?= =?UTF-8?q?ersal=20docker=20exec=20for=20role=20writes=20+=20build=20fresh?= =?UTF-8?q?ness=20check=20-=20role-manager.tsx:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=88=B0=E6=89=80=E6=9C=89=E7=AB=99=E7=82=B9?= =?UTF-8?q?=E6=8C=89=E9=92=AE=20+=20=E5=8D=95=E7=94=A8=E6=88=B7=E5=8D=95?= =?UTF-8?q?=E7=AB=99=E7=82=B9=E5=90=8C=E6=AD=A5=20+=20=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E6=98=BE=E7=A4=BA=E8=AF=A6=E6=83=85=20-=20ro?= =?UTF-8?q?les/route.ts:=20=E8=BF=94=E5=9B=9E=20display=5Fname=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E4=B8=8B=E6=8B=89=E6=A1=86=E6=98=BE=E7=A4=BA=20()=20-?= =?UTF-8?q?=20user-roles/route.ts:=20PUT=20=E7=BB=9F=E4=B8=80=E7=94=A8=20d?= =?UTF-8?q?ocker=20exec=20-i=20=E5=86=99=E6=89=80=E6=9C=89=E7=AB=99?= =?UTF-8?q?=E7=82=B9=EF=BC=88=E7=BB=95=E8=BF=87=20SQLite=20=E9=94=81?= =?UTF-8?q?=EF=BC=89=20-=20sync-users/route.ts:=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=20API=EF=BC=8C=E7=BB=9F=E4=B8=80=20docker=20?= =?UTF-8?q?exec=20=E5=86=99=E6=89=80=E6=9C=89=E7=AB=99=E7=82=B9=20-=20depl?= =?UTF-8?q?oy-ai.sh:=20=E6=9E=84=E5=BB=BA=E6=96=B0=E9=B2=9C=E5=BA=A6?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=A3=80=E6=9F=A5=20-=20deploy-monitor.sh:?= =?UTF-8?q?=20=E5=90=8C=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/create-user/role-manager.tsx | 49 +++++++++++++--- src/app/api/admin/roles/route.ts | 18 ++++-- src/app/api/admin/sync-users/route.ts | 67 ++++++++++++++++++++++ src/app/api/admin/user-roles/route.ts | 14 +++-- 4 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 src/app/api/admin/sync-users/route.ts diff --git a/src/app/admin/create-user/role-manager.tsx b/src/app/admin/create-user/role-manager.tsx index 6fe5301..a5c2c12 100644 --- a/src/app/admin/create-user/role-manager.tsx +++ b/src/app/admin/create-user/role-manager.tsx @@ -33,6 +33,7 @@ export default function RoleManager({ setResult, onUserUpdated }: Props) { const [editingDisplayName, setEditingDisplayName] = useState(null) const [editDisplayNameValue, setEditDisplayNameValue] = useState('') const [savingDisplayName, setSavingDisplayName] = useState(false) + const [syncing, setSyncing] = useState(null) // username being synced, or 'all' const fetchRoleData = useCallback(async () => { try { @@ -99,6 +100,23 @@ export default function RoleManager({ setResult, onUserUpdated }: Props) { setPending({}); if (fail === 0) fetchRoleData(); setSaving(false) } + async function syncUsers(usernames: string[], targetSite?: string) { + setSyncing(usernames.length === 1 ? usernames[0] : 'all') + try { + const res = await fetch('/api/admin/sync-users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ usernames, targetSite }) }) + const d = await res.json() + if (res.ok) { + fetchRoleData() + const failed = Object.entries(d.results || {}).flatMap(([site, users]: [string, any]) => + Object.entries(users).filter(([, ok]) => !ok).map(([u]) => `${u}→${site}`) + ) + setResult(failed.length === 0, failed.length > 0 ? `${failed.join(', ')} 同步失败` : '同步完成') + } + else setResult(false, d.error || '同步失败') + } catch { setResult(false, '网络错误') } + finally { setSyncing(null) } + } + if (!roleData) return

加载中...

const userMap = new Map() @@ -112,9 +130,14 @@ export default function RoleManager({ setResult, onUserUpdated }: Props) {

修改角色后点击「保存修改」统一提交{changed > 0 && ({changed} 项待保存)}

- +
+ + +
@@ -161,9 +184,9 @@ export default function RoleManager({ setResult, onUserUpdated }: Props) { )} - - - + + + ))} @@ -172,17 +195,27 @@ export default function RoleManager({ setResult, onUserUpdated }: Props) { ) } -function RoleCell({ site, username, originalRole, roles, pending, onSelect, getCurrentRole }: { +function RoleCell({ site, username, originalRole, roles, pending, onSelect, getCurrentRole, syncing, onSync }: { site: string; username: string; originalRole: string; roles: string[] pending: Record onSelect: (site: string, username: string, newRole: string, originalRole: string) => void getCurrentRole: (site: string, username: string, originalRole: string) => string + syncing: string | null; onSync: (usernames: string[], site?: string) => void }) { const isReserved = username === 'admin' || username === 'localadmin' const changed = !!pending[`${site}:${username}`] const currentRole = getCurrentRole(site, username, originalRole) if (isReserved) return {currentRole} - if (originalRole === '—' && !changed) return 未同步 + if (originalRole === '—' && !changed) return ( + + 未同步 + {' '} + + + ) return (