monitor-ai/src/app/admin/roles/page.tsx

131 lines
5.7 KiB
TypeScript

'use client'
// src/app/admin/roles/page.tsx — 角色权限管理
import { useState, useEffect, useCallback } from 'react'
import { Shield, RotateCw, Save } from 'lucide-react'
interface RoleData { role: string; permissions: { key: string; enabled: boolean }[] }
export default function RolesPage() {
const [roles, setRoles] = useState<RoleData[]>([])
const [allPermissions, setAllPermissions] = useState<string[]>([])
const [loading, setLoading] = useState(true)
const [editing, setEditing] = useState<string | null>(null)
const [perms, setPerms] = useState<string[]>([])
const [saving, setSaving] = useState(false)
const [toast, setToast] = useState<{ type: 'ok' | 'err'; msg: string } | null>(null)
const load = useCallback(async () => {
setLoading(true)
const res = await fetch('/api/admin/roles')
if (res.ok) {
const data = await res.json()
setRoles(data.roles)
setAllPermissions(data.allPermissions)
}
setLoading(false)
}, [])
useEffect(() => { load() }, [load])
const showToast = (type: 'ok' | 'err', msg: string) => {
setToast({ type, msg })
setTimeout(() => setToast(null), 3000)
}
const startEdit = (role: RoleData) => {
setEditing(role.role)
setPerms(role.permissions.filter(p => p.enabled).map(p => p.key))
}
const togglePerm = (key: string) => {
setPerms(prev => prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key])
}
const handleSave = async () => {
if (!editing) return
setSaving(true)
try {
const res = await fetch('/api/admin/roles', {
method: 'PUT', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ role: editing, permissions: perms }),
})
if (res.ok) { showToast('ok', `${editing} 角色权限已更新`); setEditing(null); load() }
else { const d = await res.json(); showToast('err', d.error || '保存失败') }
} catch { showToast('err', '网络错误') }
setSaving(false)
}
const permLabels: Record<string, string> = {
'dashboard:view': '查看仪表盘', 'services:view': '查看服务', 'services:manage': '管理服务',
'alerts:view': '查看告警', 'alerts:manage': '管理告警渠道', 'status_history:view': '查看状态历史',
'status_history:stats': '可用率统计', 'audit:view': '查看审计', 'users:view': '查看用户',
'users:manage': '管理用户', 'roles:manage': '管理角色',
}
return (
<div className="space-y-6">
{toast && (
<div className={`fixed top-4 right-4 z-50 px-4 py-2 rounded-lg text-sm font-medium shadow-lg ${toast.type === 'ok' ? 'bg-green-600 text-white' : 'bg-red-600 text-white'}`}>
{toast.msg}
</div>
)}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-semibold font-[family-name:var(--font-display)]"></h1>
<p className="text-sm text-slate-400 mt-1">admin / editor / viewer</p>
</div>
<button onClick={load} className="px-4 py-2 text-sm border rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800 flex items-center gap-2">
<RotateCw size={14} />
</button>
</div>
{loading ? (
<div className="text-center py-12 text-slate-400">...</div>
) : (
<div className="space-y-4">
{roles.map(role => (
<div key={role.role} className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="font-semibold capitalize flex items-center gap-2">
<Shield size={18} className={role.role === 'admin' ? 'text-indigo-600' : role.role === 'editor' ? 'text-blue-600' : 'text-slate-400'} />
{role.role}
</h2>
{editing === role.role ? (
<div className="flex gap-2">
<button onClick={handleSave} disabled={saving} className="flex items-center gap-2 px-3 py-1.5 text-sm bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 disabled:opacity-50">
<Save size={14} /> {saving ? '保存中' : '保存'}
</button>
<button onClick={() => setEditing(null)} className="px-3 py-1.5 text-sm border rounded-lg"></button>
</div>
) : (
<button onClick={() => startEdit(role)} className="px-3 py-1.5 text-sm border rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800"></button>
)}
</div>
<div className="grid grid-cols-2 gap-2">
{role.role === 'admin' ? (
<span className="text-sm text-slate-400">admin {allPermissions.length} </span>
) : editing === role.role ? (
allPermissions.map(p => (
<label key={p} className="flex items-center gap-2 text-sm py-1">
<input type="checkbox" checked={perms.includes(p)} onChange={() => togglePerm(p)}
className="rounded border-slate-300 text-indigo-600" />
{permLabels[p] || p}
</label>
))
) : (
role.permissions.filter(p => p.enabled).map(p => (
<span key={p.key} className="px-2 py-1 rounded bg-slate-50 dark:bg-slate-800 text-xs text-slate-600 dark:text-slate-400">
{permLabels[p.key] || p.key}
</span>
))
)}
</div>
</div>
))}
</div>
)}
</div>
)
}