'use client' import { useEffect, useState } from 'react' import { useSession } from 'next-auth/react' type SystemName = 'gitea' | 'portainer' | 'traefik' | 'keycloak' type AccessLevel = 'admin' | 'user' | 'readonly' interface Permission { id: number employee_id: number system_name: SystemName access_level: AccessLevel granted_at: string granted_by?: number granted_by_name?: string } interface PermissionsTabProps { employeeId: number } interface SystemInfo { name: SystemName display_name: string description: string url: string icon: string } const SYSTEM_INFO: Record = { gitea: { name: 'gitea', display_name: 'Gitea', description: 'Git 代碼託管平台', url: 'https://git.lab.taipei', icon: '📦', }, portainer: { name: 'portainer', display_name: 'Portainer', description: 'Docker 容器管理平台', url: 'https://portainer.lab.taipei', icon: '🐳', }, traefik: { name: 'traefik', display_name: 'Traefik', description: '反向代理與負載均衡', url: 'https://traefik.lab.taipei', icon: '🔀', }, keycloak: { name: 'keycloak', display_name: 'Keycloak', description: 'SSO 認證服務', url: 'https://auth.ease.taipei', icon: '🔐', }, } const ACCESS_LEVEL_LABELS: Record = { admin: '管理員', user: '使用者', readonly: '唯讀', } const ACCESS_LEVEL_COLORS: Record = { admin: 'bg-red-100 text-red-800', user: 'bg-blue-100 text-blue-800', readonly: 'bg-gray-100 text-gray-800', } export default function PermissionsTab({ employeeId }: PermissionsTabProps) { const { data: session } = useSession() const [permissions, setPermissions] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showAddForm, setShowAddForm] = useState(false) const [newPermission, setNewPermission] = useState({ system_name: 'gitea' as SystemName, access_level: 'user' as AccessLevel, }) useEffect(() => { fetchPermissions() }, [employeeId]) const fetchPermissions = async () => { try { setLoading(true) setError(null) const response = await fetch( `${process.env.NEXT_PUBLIC_API_BASE_URL}/permissions/?employee_id=${employeeId}` ) if (!response.ok) { throw new Error('無法載入權限資料') } const data = await response.json() setPermissions(data.items || []) } catch (err) { setError(err instanceof Error ? err.message : '載入失敗') } finally { setLoading(false) } } const handleGrantPermission = async (e: React.FormEvent) => { e.preventDefault() // 檢查是否已有相同系統的權限 const existingPermission = permissions.find( (p) => p.system_name === newPermission.system_name ) if (existingPermission) { if ( !confirm( `此員工已有 ${SYSTEM_INFO[newPermission.system_name].display_name} 的權限 (${ACCESS_LEVEL_LABELS[existingPermission.access_level]}),是否要更新為 ${ACCESS_LEVEL_LABELS[newPermission.access_level]}?` ) ) { return } } try { const response = await fetch( `${process.env.NEXT_PUBLIC_API_BASE_URL}/permissions/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ employee_id: employeeId, system_name: newPermission.system_name, access_level: newPermission.access_level, }), } ) if (!response.ok) { const errorData = await response.json() throw new Error(errorData.detail || '授予權限失敗') } alert('權限授予成功!') setShowAddForm(false) setNewPermission({ system_name: 'gitea', access_level: 'user' }) fetchPermissions() } catch (err) { alert(err instanceof Error ? err.message : '操作失敗') } } const handleRevokePermission = async (permissionId: number, systemName: SystemName) => { if ( !confirm( `確定要撤銷此員工的 ${SYSTEM_INFO[systemName].display_name} 權限嗎?` ) ) { return } try { const response = await fetch( `${process.env.NEXT_PUBLIC_API_BASE_URL}/permissions/${permissionId}`, { method: 'DELETE', } ) if (!response.ok) { throw new Error('撤銷權限失敗') } alert('權限已撤銷') fetchPermissions() } catch (err) { alert(err instanceof Error ? err.message : '操作失敗') } } if (loading) { return (
載入中...
) } if (error) { return (
{error}
) } // 計算已授予和未授予的系統 const grantedSystems = new Set(permissions.map((p) => p.system_name)) const availableSystems = Object.keys(SYSTEM_INFO).filter( (sys) => !grantedSystems.has(sys as SystemName) ) as SystemName[] return (
{/* 標題與新增按鈕 */}

系統權限列表

{availableSystems.length > 0 && ( )}
{/* 授予權限表單 */} {showAddForm && (

授予系統權限

權限說明:

  • 唯讀: 只能查看資料,無法修改
  • 使用者: 可建立、編輯自己的資源
  • 管理員: 完整控制權限,包括管理其他用戶
)} {/* 權限列表 */} {permissions.length === 0 ? (

此員工尚未被授予任何系統權限

點擊上方「授予權限」按鈕來添加

) : (
{permissions.map((permission) => { const systemInfo = SYSTEM_INFO[permission.system_name] return (
{systemInfo.icon}

{systemInfo.display_name}

{systemInfo.description}

{ACCESS_LEVEL_LABELS[permission.access_level]}
授予時間:{' '} {new Date(permission.granted_at).toLocaleString('zh-TW')}
{permission.granted_by_name && (
授予人:{' '} {permission.granted_by_name}
)}
開啟系統
) })}
)} {/* 系統說明 */}

🔒 權限管理說明

  • • 所有系統權限與 Keycloak Groups 同步,員工登入後自動生效
  • • 權限撤銷後,員工將立即失去對該系統的存取權
  • • 建議遵循最小權限原則,僅授予必要的權限
  • • 所有權限變更都會記錄在審計日誌中
) }