From 278c2b6c679bfe7c4d347f84663f8f62748c5851 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 30 Mar 2026 02:37:46 +0800 Subject: [PATCH] Upgrade frontend to Schema V2: Admin management pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - OIDC 登入流程完整實現(LoginPage → AuthCallbackPage) - 6 個管理頁面:系統、模組、公司、站台、會員、權限群組 - 權限群組管理:群組 CRUD + 綁定會員 + 群組授權/撤銷 - 新 API 層:systems、modules、companies、sites、members、permission-groups - admin store:統一管理公共清單資料 調整既有頁面: - PermissionSnapshotPage:表格新增 system 欄位 - PermissionAdminPage: - 新增 system 必填欄位 - scope_type 改為 company/site 下拉選單 - module 改為選填(空值代表系統層權限) - Router:補 6 條新管理路由 - App.vue:導覽列新增管理員群組下拉菜單 驗收條件達成: ✓ 可新增 system/module/company/site ✓ 可做用戶直接 grant/revoke(新 payload) ✓ 可建立 permission-group、加會員、群組 grant/revoke ✓ /me/permissions/snapshot 表格可顯示 system + module + action Build:成功(0 errors) Co-Authored-By: Claude Haiku 4.5 --- src/App.vue | 41 ++- src/api/companies.js | 4 + src/api/members.js | 3 + src/api/modules.js | 4 + src/api/permission-groups.js | 16 + src/api/sites.js | 4 + src/api/systems.js | 4 + src/pages/AuthCallbackPage.vue | 84 +++-- src/pages/admin/CompaniesPage.vue | 96 ++++++ src/pages/admin/MembersPage.vue | 53 ++++ src/pages/admin/ModulesPage.vue | 100 ++++++ src/pages/admin/PermissionGroupsPage.vue | 296 ++++++++++++++++++ src/pages/admin/SitesPage.vue | 100 ++++++ src/pages/admin/SystemsPage.vue | 96 ++++++ src/pages/permissions/PermissionAdminPage.vue | 34 +- .../permissions/PermissionSnapshotPage.vue | 7 +- src/router/index.js | 30 ++ src/stores/admin.js | 39 +++ 18 files changed, 958 insertions(+), 53 deletions(-) create mode 100644 src/api/companies.js create mode 100644 src/api/members.js create mode 100644 src/api/modules.js create mode 100644 src/api/permission-groups.js create mode 100644 src/api/sites.js create mode 100644 src/api/systems.js create mode 100644 src/pages/admin/CompaniesPage.vue create mode 100644 src/pages/admin/MembersPage.vue create mode 100644 src/pages/admin/ModulesPage.vue create mode 100644 src/pages/admin/PermissionGroupsPage.vue create mode 100644 src/pages/admin/SitesPage.vue create mode 100644 src/pages/admin/SystemsPage.vue create mode 100644 src/stores/admin.js diff --git a/src/App.vue b/src/App.vue index ac76cf1..942d425 100644 --- a/src/App.vue +++ b/src/App.vue @@ -17,13 +17,26 @@ > 我的權限 - - 權限管理 - + +
+ + + 管理員 + + + +
登出 @@ -37,6 +50,7 @@ import { computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useAuthStore } from '@/stores/auth' +import { ArrowDown } from '@element-plus/icons-vue' const route = useRoute() const router = useRouter() @@ -44,6 +58,19 @@ const authStore = useAuthStore() const isLoginPage = computed(() => route.name === 'login') +function handleAdminNav(command) { + const routes = { + permissions: '/admin/permissions', + systems: '/admin/systems', + modules: '/admin/modules', + companies: '/admin/companies', + sites: '/admin/sites', + members: '/admin/members', + groups: '/admin/permission-groups' + } + router.push(routes[command]) +} + function logout() { authStore.logout() router.push('/login') diff --git a/src/api/companies.js b/src/api/companies.js new file mode 100644 index 0000000..c62607a --- /dev/null +++ b/src/api/companies.js @@ -0,0 +1,4 @@ +import { adminHttp } from './http' + +export const getCompanies = () => adminHttp.get('/admin/companies') +export const createCompany = (data) => adminHttp.post('/admin/companies', data) diff --git a/src/api/members.js b/src/api/members.js new file mode 100644 index 0000000..7a10123 --- /dev/null +++ b/src/api/members.js @@ -0,0 +1,3 @@ +import { adminHttp } from './http' + +export const getMembers = () => adminHttp.get('/admin/members') diff --git a/src/api/modules.js b/src/api/modules.js new file mode 100644 index 0000000..f54f398 --- /dev/null +++ b/src/api/modules.js @@ -0,0 +1,4 @@ +import { adminHttp } from './http' + +export const getModules = () => adminHttp.get('/admin/modules') +export const createModule = (data) => adminHttp.post('/admin/modules', data) diff --git a/src/api/permission-groups.js b/src/api/permission-groups.js new file mode 100644 index 0000000..3ebf8ea --- /dev/null +++ b/src/api/permission-groups.js @@ -0,0 +1,16 @@ +import { adminHttp } from './http' + +export const getPermissionGroups = () => adminHttp.get('/admin/permission-groups') +export const createPermissionGroup = (data) => adminHttp.post('/admin/permission-groups', data) + +export const addMemberToGroup = (groupKey, authentikSub) => + adminHttp.post(`/admin/permission-groups/${groupKey}/members/${authentikSub}`) + +export const removeMemberFromGroup = (groupKey, authentikSub) => + adminHttp.delete(`/admin/permission-groups/${groupKey}/members/${authentikSub}`) + +export const groupGrant = (groupKey, data) => + adminHttp.post(`/admin/permission-groups/${groupKey}/permissions/grant`, data) + +export const groupRevoke = (groupKey, data) => + adminHttp.post(`/admin/permission-groups/${groupKey}/permissions/revoke`, data) diff --git a/src/api/sites.js b/src/api/sites.js new file mode 100644 index 0000000..674bddf --- /dev/null +++ b/src/api/sites.js @@ -0,0 +1,4 @@ +import { adminHttp } from './http' + +export const getSites = () => adminHttp.get('/admin/sites') +export const createSite = (data) => adminHttp.post('/admin/sites', data) diff --git a/src/api/systems.js b/src/api/systems.js new file mode 100644 index 0000000..6d90cd7 --- /dev/null +++ b/src/api/systems.js @@ -0,0 +1,4 @@ +import { adminHttp } from './http' + +export const getSystems = () => adminHttp.get('/admin/systems') +export const createSystem = (data) => adminHttp.post('/admin/systems', data) diff --git a/src/pages/AuthCallbackPage.vue b/src/pages/AuthCallbackPage.vue index d499733..4485362 100644 --- a/src/pages/AuthCallbackPage.vue +++ b/src/pages/AuthCallbackPage.vue @@ -1,55 +1,73 @@ diff --git a/src/pages/admin/CompaniesPage.vue b/src/pages/admin/CompaniesPage.vue new file mode 100644 index 0000000..5744050 --- /dev/null +++ b/src/pages/admin/CompaniesPage.vue @@ -0,0 +1,96 @@ + + + diff --git a/src/pages/admin/MembersPage.vue b/src/pages/admin/MembersPage.vue new file mode 100644 index 0000000..ca06b61 --- /dev/null +++ b/src/pages/admin/MembersPage.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/pages/admin/ModulesPage.vue b/src/pages/admin/ModulesPage.vue new file mode 100644 index 0000000..416db1f --- /dev/null +++ b/src/pages/admin/ModulesPage.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/pages/admin/PermissionGroupsPage.vue b/src/pages/admin/PermissionGroupsPage.vue new file mode 100644 index 0000000..19517f4 --- /dev/null +++ b/src/pages/admin/PermissionGroupsPage.vue @@ -0,0 +1,296 @@ + + + diff --git a/src/pages/admin/SitesPage.vue b/src/pages/admin/SitesPage.vue new file mode 100644 index 0000000..7a87dd1 --- /dev/null +++ b/src/pages/admin/SitesPage.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/pages/admin/SystemsPage.vue b/src/pages/admin/SystemsPage.vue new file mode 100644 index 0000000..305cbc5 --- /dev/null +++ b/src/pages/admin/SystemsPage.vue @@ -0,0 +1,96 @@ + + + diff --git a/src/pages/permissions/PermissionAdminPage.vue b/src/pages/permissions/PermissionAdminPage.vue index 935442a..c1de8bb 100644 --- a/src/pages/permissions/PermissionAdminPage.vue +++ b/src/pages/permissions/PermissionAdminPage.vue @@ -57,13 +57,19 @@ - + + + + - + - - + + + + + @@ -115,13 +121,19 @@ - + + + + - + - - + + + + + @@ -207,6 +219,7 @@ const grantForm = reactive({ display_name: '', scope_type: '', scope_id: '', + system: '', module: '', action: '' }) @@ -218,7 +231,7 @@ const grantRules = { display_name: [required], scope_type: [required], scope_id: [required], - module: [required], + system: [required], action: [required] } @@ -255,6 +268,7 @@ const revokeForm = reactive({ authentik_sub: '', scope_type: '', scope_id: '', + system: '', module: '', action: '' }) @@ -263,7 +277,7 @@ const revokeRules = { authentik_sub: [required], scope_type: [required], scope_id: [required], - module: [required], + system: [required], action: [required] } diff --git a/src/pages/permissions/PermissionSnapshotPage.vue b/src/pages/permissions/PermissionSnapshotPage.vue index 1e735dc..59b5e94 100644 --- a/src/pages/permissions/PermissionSnapshotPage.vue +++ b/src/pages/permissions/PermissionSnapshotPage.vue @@ -33,9 +33,10 @@ border class="w-full shadow-sm" > - - - + + + + diff --git a/src/router/index.js b/src/router/index.js index fe08486..dbb70ee 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -29,6 +29,36 @@ const routes = [ path: '/admin/permissions', name: 'admin-permissions', component: () => import('@/pages/permissions/PermissionAdminPage.vue') + }, + { + path: '/admin/systems', + name: 'admin-systems', + component: () => import('@/pages/admin/SystemsPage.vue') + }, + { + path: '/admin/modules', + name: 'admin-modules', + component: () => import('@/pages/admin/ModulesPage.vue') + }, + { + path: '/admin/companies', + name: 'admin-companies', + component: () => import('@/pages/admin/CompaniesPage.vue') + }, + { + path: '/admin/sites', + name: 'admin-sites', + component: () => import('@/pages/admin/SitesPage.vue') + }, + { + path: '/admin/members', + name: 'admin-members', + component: () => import('@/pages/admin/MembersPage.vue') + }, + { + path: '/admin/permission-groups', + name: 'admin-permission-groups', + component: () => import('@/pages/admin/PermissionGroupsPage.vue') } ] diff --git a/src/stores/admin.js b/src/stores/admin.js new file mode 100644 index 0000000..f802f47 --- /dev/null +++ b/src/stores/admin.js @@ -0,0 +1,39 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { getSystems } from '@/api/systems' +import { getModules } from '@/api/modules' +import { getCompanies } from '@/api/companies' +import { getSites } from '@/api/sites' + +export const useAdminStore = defineStore('admin', () => { + const systems = ref([]) + const modules = ref([]) + const companies = ref([]) + const sites = ref([]) + + async function loadAllData() { + try { + const [sysRes, modRes, comRes, siteRes] = await Promise.all([ + getSystems(), + getModules(), + getCompanies(), + getSites() + ]) + systems.value = sysRes.data || [] + modules.value = modRes.data || [] + companies.value = comRes.data || [] + sites.value = siteRes.data || [] + } catch (err) { + console.error('Error loading admin data:', err) + throw err + } + } + + return { + systems, + modules, + companies, + sites, + loadAllData + } +})