diff --git a/backend/app/api/admin_catalog.py b/backend/app/api/admin_catalog.py index 818a775..a7b54a7 100644 --- a/backend/app/api/admin_catalog.py +++ b/backend/app/api/admin_catalog.py @@ -13,6 +13,10 @@ from app.repositories.users_repo import UsersRepository from app.schemas.catalog import ( CompanyCreateRequest, CompanyItem, + GroupBindingSnapshot, + GroupBindingUpdateRequest, + GroupRelationItem, + MemberRelationItem, CompanyUpdateRequest, MemberItem, MemberPermissionGroupsResponse, @@ -69,6 +73,14 @@ def _resolve_scope_ids(db: Session, scope_type: str, scope_id: str) -> tuple[str raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="invalid_scope_type") +def _split_module_key(payload_module: str | None) -> str: + if not payload_module: + return "__system__" + if "." in payload_module: + return payload_module.split(".", 1)[1] + return payload_module + + def _sync_member_to_authentik( *, authentik_sub: str, @@ -195,6 +207,96 @@ def update_module( return ModuleItem(id=row.id, system_key=system_key, module_key=row.module_key, name=row.name, status=row.status) +@router.get("/systems/{system_key}/groups") +def list_system_groups( + system_key: str, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> dict[str, list[dict]]: + systems_repo = SystemsRepository(db) + groups_repo = PermissionGroupsRepository(db) + if not systems_repo.get_by_key(system_key): + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="system_not_found") + groups = groups_repo.list_system_groups(system_key) + return { + "items": [ + GroupRelationItem(group_key=g.group_key, group_name=g.name, status=g.status).model_dump() + for g in groups + ] + } + + +@router.get("/systems/{system_key}/members") +def list_system_members( + system_key: str, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> dict[str, list[dict]]: + systems_repo = SystemsRepository(db) + groups_repo = PermissionGroupsRepository(db) + if not systems_repo.get_by_key(system_key): + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="system_not_found") + members = groups_repo.list_system_members(system_key) + return { + "items": [ + MemberRelationItem( + authentik_sub=m.authentik_sub, + email=m.email, + display_name=m.display_name, + is_active=m.is_active, + ).model_dump() + for m in members + ] + } + + +@router.get("/modules/{module_key}/groups") +def list_module_groups( + module_key: str, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> dict[str, list[dict]]: + modules_repo = ModulesRepository(db) + groups_repo = PermissionGroupsRepository(db) + module = modules_repo.get_by_key(module_key) + if not module: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="module_not_found") + system_key, module_name = module.module_key.split(".", 1) if "." in module.module_key else ("", module.module_key) + groups = groups_repo.list_module_groups(system_key, module_name) + return { + "items": [ + GroupRelationItem(group_key=g.group_key, group_name=g.name, status=g.status).model_dump() + for g in groups + ] + } + + +@router.get("/modules/{module_key}/members") +def list_module_members( + module_key: str, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> dict[str, list[dict]]: + modules_repo = ModulesRepository(db) + groups_repo = PermissionGroupsRepository(db) + module = modules_repo.get_by_key(module_key) + if not module: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="module_not_found") + system_key, module_name = module.module_key.split(".", 1) if "." in module.module_key else ("", module.module_key) + members = groups_repo.list_module_members(system_key, module_name) + return { + "items": [ + MemberRelationItem( + authentik_sub=m.authentik_sub, + email=m.email, + display_name=m.display_name, + is_active=m.is_active, + ).model_dump() + for m in members + ] + } + + @router.get("/companies") def list_companies( _: ApiClient = Depends(require_api_client), @@ -236,6 +338,32 @@ def update_company( return CompanyItem(id=row.id, company_key=row.company_key, name=row.name, status=row.status) +@router.get("/companies/{company_key}/sites") +def list_company_sites( + company_key: str, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> dict[str, list[dict]]: + companies_repo = CompaniesRepository(db) + sites_repo = SitesRepository(db) + company = companies_repo.get_by_key(company_key) + if not company: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="company_not_found") + items, _ = sites_repo.list(company_id=company.id, limit=1000, offset=0) + return { + "items": [ + SiteItem( + id=i.id, + site_key=i.site_key, + company_key=company.company_key, + name=i.name, + status=i.status, + ).model_dump() + for i in items + ] + } + + @router.get("/sites") def list_sites( _: ApiClient = Depends(require_api_client), @@ -481,16 +609,78 @@ def list_permission_group_permissions( PermissionGroupPermissionItem( id=r.id, system=r.system, - module=r.module, + module=("" if r.module == "__system__" else (r.module if "." in r.module else f"{r.system}.{r.module}")), action=r.action, scope_type=r.scope_type, scope_id=r.scope_id, ).model_dump() for r in rows + if r.action in {"view", "edit"} and r.scope_type == "site" ] } +@router.get("/permission-groups/{group_key}/bindings", response_model=GroupBindingSnapshot) +def get_permission_group_bindings( + group_key: str, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> GroupBindingSnapshot: + repo = PermissionGroupsRepository(db) + group = repo.get_by_key(group_key) + if not group: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found") + snapshot = repo.get_group_binding_snapshot(group.id, group_key) + return GroupBindingSnapshot(**snapshot) + + +@router.put("/permission-groups/{group_key}/bindings", response_model=GroupBindingSnapshot) +def replace_permission_group_bindings( + group_key: str, + payload: GroupBindingUpdateRequest, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> GroupBindingSnapshot: + repo = PermissionGroupsRepository(db) + sites_repo = SitesRepository(db) + systems_repo = SystemsRepository(db) + modules_repo = ModulesRepository(db) + + group = repo.get_by_key(group_key) + if not group: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found") + + site_keys = list(dict.fromkeys(payload.site_keys)) + system_keys = list(dict.fromkeys(payload.system_keys)) + module_keys = list(dict.fromkeys(payload.module_keys)) + + valid_sites = {s.site_key for s in sites_repo.list(limit=10000, offset=0)[0]} + missing_sites = [k for k in site_keys if k not in valid_sites] + if missing_sites: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"site_not_found:{','.join(missing_sites)}") + + valid_systems = {s.system_key for s in systems_repo.list(limit=10000, offset=0)[0]} + missing_systems = [k for k in system_keys if k not in valid_systems] + if missing_systems: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"system_not_found:{','.join(missing_systems)}") + + valid_modules = {m.module_key for m in modules_repo.list(limit=10000, offset=0)[0]} + missing_modules = [k for k in module_keys if k not in valid_modules] + if missing_modules: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"module_not_found:{','.join(missing_modules)}") + + repo.replace_group_bindings( + group_id=group.id, + site_keys=site_keys, + system_keys=system_keys, + module_keys=module_keys, + member_subs=payload.member_subs, + actions=payload.actions, + ) + snapshot = repo.get_group_binding_snapshot(group.id, group_key) + return GroupBindingSnapshot(**snapshot) + + @router.post("/permission-groups", response_model=PermissionGroupItem) def create_permission_group( payload: PermissionGroupCreateRequest, @@ -562,11 +752,11 @@ def grant_group_permission( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found") _resolve_module_id(db, payload.system, payload.module) _resolve_scope_ids(db, payload.scope_type, payload.scope_id) - module_key = f"{payload.system}.{payload.module}" if payload.module else f"{payload.system}.__system__" + module_name = _split_module_key(payload.module) row = groups_repo.grant_group_permission( group_id=group.id, system=payload.system, - module=module_key, + module=module_name, action=payload.action, scope_type=payload.scope_type, scope_id=payload.scope_id, @@ -587,11 +777,11 @@ def revoke_group_permission( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found") _resolve_module_id(db, payload.system, payload.module) _resolve_scope_ids(db, payload.scope_type, payload.scope_id) - module_key = f"{payload.system}.{payload.module}" if payload.module else f"{payload.system}.__system__" + module_name = _split_module_key(payload.module) deleted = groups_repo.revoke_group_permission( group_id=group.id, system=payload.system, - module=module_key, + module=module_name, action=payload.action, scope_type=payload.scope_type, scope_id=payload.scope_id, diff --git a/backend/app/repositories/permission_groups_repo.py b/backend/app/repositories/permission_groups_repo.py index ff2477f..289217b 100644 --- a/backend/app/repositories/permission_groups_repo.py +++ b/backend/app/repositories/permission_groups_repo.py @@ -1,11 +1,14 @@ from __future__ import annotations +from collections import defaultdict + from sqlalchemy import delete, func, select from sqlalchemy.orm import Session from app.models.permission_group import PermissionGroup from app.models.permission_group_member import PermissionGroupMember from app.models.permission_group_permission import PermissionGroupPermission +from app.models.user import User class PermissionGroupsRepository: @@ -125,6 +128,128 @@ class PermissionGroupsRepository: ) return list(self.db.scalars(stmt).all()) + def replace_group_bindings( + self, + *, + group_id: str, + site_keys: list[str], + system_keys: list[str], + module_keys: list[str], + member_subs: list[str], + actions: list[str], + ) -> None: + normalized_sites = list(dict.fromkeys([s for s in site_keys if s])) + normalized_actions = [a for a in list(dict.fromkeys(actions)) if a in {"view", "edit"}] + normalized_member_subs = list(dict.fromkeys([s for s in member_subs if s])) + + modules_by_system: dict[str, list[str]] = defaultdict(list) + for full_module_key in list(dict.fromkeys([m for m in module_keys if m])): + if "." not in full_module_key: + continue + system_key, module_name = full_module_key.split(".", 1) + if module_name == "__system__": + continue + modules_by_system[system_key].append(module_name) + + normalized_systems = set([s for s in system_keys if s]) + normalized_systems.update(modules_by_system.keys()) + + self.db.execute(delete(PermissionGroupPermission).where(PermissionGroupPermission.group_id == group_id)) + self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.group_id == group_id)) + + for sub in normalized_member_subs: + self.db.add(PermissionGroupMember(group_id=group_id, authentik_sub=sub)) + + for site_key in normalized_sites: + for action in normalized_actions: + for system_key in sorted(normalized_systems): + module_names = modules_by_system.get(system_key) or ["__system__"] + for module_name in module_names: + self.db.add( + PermissionGroupPermission( + group_id=group_id, + system=system_key, + module=module_name, + action=action, + scope_type="site", + scope_id=site_key, + ) + ) + + self.db.commit() + + def get_group_binding_snapshot(self, group_id: str, group_key: str) -> dict: + permissions = self.list_group_permissions(group_id) + site_keys = sorted({p.scope_id for p in permissions if p.scope_type == "site"}) + system_keys = sorted({p.system for p in permissions}) + actions = sorted({p.action for p in permissions if p.action in {"view", "edit"}}) + module_keys = sorted( + { + p.module if "." in p.module else f"{p.system}.{p.module}" + for p in permissions + if p.module and p.module != "__system__" + } + ) + member_subs = sorted(self.list_group_member_subs(group_id)) + return { + "group_key": group_key, + "site_keys": site_keys, + "system_keys": system_keys, + "module_keys": module_keys, + "member_subs": member_subs, + "actions": actions, + } + + def list_group_member_subs(self, group_id: str) -> list[str]: + stmt = ( + select(PermissionGroupMember.authentik_sub) + .where(PermissionGroupMember.group_id == group_id) + .order_by(PermissionGroupMember.authentik_sub.asc()) + ) + return [row[0] for row in self.db.execute(stmt).all()] + + def list_system_groups(self, system_key: str) -> list[PermissionGroup]: + stmt = ( + select(PermissionGroup) + .join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroup.id) + .where(PermissionGroupPermission.system == system_key) + .order_by(PermissionGroup.name.asc()) + .distinct() + ) + return list(self.db.scalars(stmt).all()) + + def list_system_members(self, system_key: str) -> list[User]: + stmt = ( + select(User) + .join(PermissionGroupMember, PermissionGroupMember.authentik_sub == User.authentik_sub) + .join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroupMember.group_id) + .where(PermissionGroupPermission.system == system_key) + .order_by(User.email.asc(), User.authentik_sub.asc()) + .distinct() + ) + return list(self.db.scalars(stmt).all()) + + def list_module_groups(self, system_key: str, module_name: str) -> list[PermissionGroup]: + stmt = ( + select(PermissionGroup) + .join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroup.id) + .where(PermissionGroupPermission.system == system_key, PermissionGroupPermission.module == module_name) + .order_by(PermissionGroup.name.asc()) + .distinct() + ) + return list(self.db.scalars(stmt).all()) + + def list_module_members(self, system_key: str, module_name: str) -> list[User]: + stmt = ( + select(User) + .join(PermissionGroupMember, PermissionGroupMember.authentik_sub == User.authentik_sub) + .join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroupMember.group_id) + .where(PermissionGroupPermission.system == system_key, PermissionGroupPermission.module == module_name) + .order_by(User.email.asc(), User.authentik_sub.asc()) + .distinct() + ) + return list(self.db.scalars(stmt).all()) + def revoke_group_permission( self, group_id: str, diff --git a/backend/app/repositories/permissions_repo.py b/backend/app/repositories/permissions_repo.py index 014cd56..ce0a78c 100644 --- a/backend/app/repositories/permissions_repo.py +++ b/backend/app/repositories/permissions_repo.py @@ -29,6 +29,8 @@ class PermissionsRepository: .join(Company, Company.id == UserScopePermission.company_id, isouter=True) .join(Site, Site.id == UserScopePermission.site_id, isouter=True) .where(UserScopePermission.user_id == user_id) + .where(UserScopePermission.action.in_(["view", "edit"])) + .where(UserScopePermission.scope_type == "site") ) group_stmt = ( select( @@ -42,6 +44,8 @@ class PermissionsRepository: .select_from(PermissionGroupPermission) .join(PermissionGroupMember, PermissionGroupMember.group_id == PermissionGroupPermission.group_id) .where(PermissionGroupMember.authentik_sub == authentik_sub) + .where(PermissionGroupPermission.action.in_(["view", "edit"])) + .where(PermissionGroupPermission.scope_type == "site") ) rows = self.db.execute(direct_stmt).all() + self.db.execute(group_stmt).all() result: list[tuple[str, str, str | None, str, str]] = [] @@ -50,6 +54,10 @@ class PermissionsRepository: source = row[0] if source == "group": _, scope_type, scope_id, system_key, module_key, action = row + if module_key == "__system__": + module_key = f"{system_key}.__system__" + elif module_key and "." not in module_key: + module_key = f"{system_key}.{module_key}" else: _, scope_type, company_key, site_key, module_key, action = row scope_id = company_key if scope_type == "company" else site_key @@ -147,6 +155,8 @@ class PermissionsRepository: .join(Module, Module.id == UserScopePermission.module_id) .join(Company, Company.id == UserScopePermission.company_id, isouter=True) .join(Site, Site.id == UserScopePermission.site_id, isouter=True) + .where(UserScopePermission.action.in_(["view", "edit"])) + .where(UserScopePermission.scope_type == "site") ) count_stmt = ( select(func.count()) @@ -155,9 +165,11 @@ class PermissionsRepository: .join(Module, Module.id == UserScopePermission.module_id) .join(Company, Company.id == UserScopePermission.company_id, isouter=True) .join(Site, Site.id == UserScopePermission.site_id, isouter=True) + .where(UserScopePermission.action.in_(["view", "edit"])) + .where(UserScopePermission.scope_type == "site") ) - if scope_type in {"company", "site"}: + if scope_type == "site": stmt = stmt.where(UserScopePermission.scope_type == scope_type) count_stmt = count_stmt.where(UserScopePermission.scope_type == scope_type) diff --git a/backend/app/schemas/catalog.py b/backend/app/schemas/catalog.py index ad58ad8..90af683 100644 --- a/backend/app/schemas/catalog.py +++ b/backend/app/schemas/catalog.py @@ -1,4 +1,5 @@ from pydantic import BaseModel +from typing import Literal class SystemCreateRequest(BaseModel): @@ -134,11 +135,41 @@ class PermissionGroupPermissionItem(BaseModel): id: str system: str module: str - action: str - scope_type: str + action: Literal["view", "edit"] + scope_type: Literal["site"] scope_id: str class MemberPermissionGroupsResponse(BaseModel): authentik_sub: str group_keys: list[str] + + +class GroupBindingUpdateRequest(BaseModel): + site_keys: list[str] + system_keys: list[str] + module_keys: list[str] + member_subs: list[str] + actions: list[Literal["view", "edit"]] + + +class GroupBindingSnapshot(BaseModel): + group_key: str + site_keys: list[str] + system_keys: list[str] + module_keys: list[str] + member_subs: list[str] + actions: list[Literal["view", "edit"]] + + +class GroupRelationItem(BaseModel): + group_key: str + group_name: str + status: str + + +class MemberRelationItem(BaseModel): + authentik_sub: str + email: str | None = None + display_name: str | None = None + is_active: bool diff --git a/backend/app/schemas/permissions.py b/backend/app/schemas/permissions.py index a6ba358..2b83224 100644 --- a/backend/app/schemas/permissions.py +++ b/backend/app/schemas/permissions.py @@ -1,34 +1,38 @@ from datetime import datetime +from typing import Literal from pydantic import BaseModel +ActionType = Literal["view", "edit"] +ScopeType = Literal["site"] + class PermissionGrantRequest(BaseModel): authentik_sub: str email: str | None = None display_name: str | None = None - scope_type: str + scope_type: ScopeType scope_id: str system: str module: str | None = None - action: str + action: ActionType class PermissionRevokeRequest(BaseModel): authentik_sub: str - scope_type: str + scope_type: ScopeType scope_id: str system: str module: str | None = None - action: str + action: ActionType class PermissionItem(BaseModel): - scope_type: str + scope_type: ScopeType scope_id: str system: str | None = None module: str - action: str + action: ActionType class PermissionSnapshotResponse(BaseModel): @@ -41,11 +45,11 @@ class DirectPermissionRow(BaseModel): authentik_sub: str email: str | None = None display_name: str | None = None - scope_type: str + scope_type: ScopeType scope_id: str system: str | None = None module: str | None = None - action: str + action: ActionType created_at: datetime diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 21b53cf..92e211f 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -56,7 +56,6 @@ const userTabs = [ ] const adminTabs = [ - { to: '/admin/permissions', label: '權限管理' }, { to: '/admin/systems', label: '系統' }, { to: '/admin/modules', label: '模組' }, { to: '/admin/companies', label: '公司' }, diff --git a/frontend/src/api/companies.js b/frontend/src/api/companies.js index 9411dd3..732817c 100644 --- a/frontend/src/api/companies.js +++ b/frontend/src/api/companies.js @@ -3,3 +3,4 @@ import { adminHttp } from './http' export const getCompanies = () => adminHttp.get('/admin/companies') export const createCompany = (data) => adminHttp.post('/admin/companies', data) export const updateCompany = (companyKey, data) => adminHttp.patch(`/admin/companies/${companyKey}`, data) +export const getCompanySites = (companyKey) => adminHttp.get(`/admin/companies/${companyKey}/sites`) diff --git a/frontend/src/api/modules.js b/frontend/src/api/modules.js index 3d0ba34..2e48669 100644 --- a/frontend/src/api/modules.js +++ b/frontend/src/api/modules.js @@ -3,3 +3,5 @@ import { adminHttp } from './http' export const getModules = () => adminHttp.get('/admin/modules') export const createModule = (data) => adminHttp.post('/admin/modules', data) export const updateModule = (moduleKey, data) => adminHttp.patch(`/admin/modules/${moduleKey}`, data) +export const getModuleGroups = (moduleKey) => adminHttp.get(`/admin/modules/${moduleKey}/groups`) +export const getModuleMembers = (moduleKey) => adminHttp.get(`/admin/modules/${moduleKey}/members`) diff --git a/frontend/src/api/permission-groups.js b/frontend/src/api/permission-groups.js index 2845f97..9d5a238 100644 --- a/frontend/src/api/permission-groups.js +++ b/frontend/src/api/permission-groups.js @@ -4,6 +4,9 @@ export const getPermissionGroups = () => adminHttp.get('/admin/permission-groups export const createPermissionGroup = (data) => adminHttp.post('/admin/permission-groups', data) export const updatePermissionGroup = (groupKey, data) => adminHttp.patch(`/admin/permission-groups/${groupKey}`, data) export const getPermissionGroupPermissions = (groupKey) => adminHttp.get(`/admin/permission-groups/${groupKey}/permissions`) +export const getPermissionGroupBindings = (groupKey) => adminHttp.get(`/admin/permission-groups/${groupKey}/bindings`) +export const updatePermissionGroupBindings = (groupKey, data) => + adminHttp.put(`/admin/permission-groups/${groupKey}/bindings`, data) export const addMemberToGroup = (groupKey, authentikSub) => adminHttp.post(`/admin/permission-groups/${groupKey}/members/${authentikSub}`) diff --git a/frontend/src/api/systems.js b/frontend/src/api/systems.js index 90cac1a..3e87b58 100644 --- a/frontend/src/api/systems.js +++ b/frontend/src/api/systems.js @@ -3,3 +3,5 @@ import { adminHttp } from './http' export const getSystems = () => adminHttp.get('/admin/systems') export const createSystem = (data) => adminHttp.post('/admin/systems', data) export const updateSystem = (systemKey, data) => adminHttp.patch(`/admin/systems/${systemKey}`, data) +export const getSystemGroups = (systemKey) => adminHttp.get(`/admin/systems/${systemKey}/groups`) +export const getSystemMembers = (systemKey) => adminHttp.get(`/admin/systems/${systemKey}/members`) diff --git a/frontend/src/pages/admin/CompaniesPage.vue b/frontend/src/pages/admin/CompaniesPage.vue index 5eca360..05f5023 100644 --- a/frontend/src/pages/admin/CompaniesPage.vue +++ b/frontend/src/pages/admin/CompaniesPage.vue @@ -13,9 +13,10 @@ - + @@ -47,6 +48,18 @@ 儲存 + + + + + + + + + + @@ -54,7 +67,7 @@ import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' import { Plus } from '@element-plus/icons-vue' -import { getCompanies, createCompany, updateCompany } from '@/api/companies' +import { getCompanies, createCompany, updateCompany, getCompanySites } from '@/api/companies' const companies = ref([]) const loading = ref(false) @@ -73,6 +86,11 @@ const rules = { name: [{ required: true, message: '請輸入名稱', trigger: 'blur' }] } +const showSitesDialog = ref(false) +const sitesLoading = ref(false) +const selectedCompanyKey = ref('') +const companySites = ref([]) + async function load() { loading.value = true error.value = false @@ -133,5 +151,19 @@ async function handleEdit() { } } +async function openSites(row) { + selectedCompanyKey.value = row.company_key + showSitesDialog.value = true + sitesLoading.value = true + try { + const res = await getCompanySites(row.company_key) + companySites.value = res.data?.items || [] + } catch (err) { + ElMessage.error('載入公司站台失敗') + } finally { + sitesLoading.value = false + } +} + onMounted(load) diff --git a/frontend/src/pages/admin/ModulesPage.vue b/frontend/src/pages/admin/ModulesPage.vue index f913b57..bd2fca9 100644 --- a/frontend/src/pages/admin/ModulesPage.vue +++ b/frontend/src/pages/admin/ModulesPage.vue @@ -19,12 +19,14 @@ - + - + @@ -69,6 +71,33 @@ 儲存 + + + + + + + + + + + + + + + + + + + + + + + + + @@ -76,7 +105,7 @@ import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' import { Plus } from '@element-plus/icons-vue' -import { getModules, createModule, updateModule } from '@/api/modules' +import { getModules, createModule, updateModule, getModuleGroups, getModuleMembers } from '@/api/modules' import { getSystems } from '@/api/systems' const modules = ref([]) @@ -98,6 +127,13 @@ const rules = { name: [{ required: true, message: '請輸入名稱', trigger: 'blur' }] } +const showRelationDialog = ref(false) +const relationLoading = ref(false) +const relationModuleKey = ref('') +const relationTab = ref('groups') +const relationGroups = ref([]) +const relationMembers = ref([]) + async function load() { loading.value = true error.value = false @@ -166,5 +202,24 @@ async function handleEdit() { } } +async function openRelations(row, tab) { + relationModuleKey.value = row.module_key + relationTab.value = tab + showRelationDialog.value = true + relationLoading.value = true + try { + const [groupsRes, membersRes] = await Promise.all([ + getModuleGroups(row.module_key), + getModuleMembers(row.module_key) + ]) + relationGroups.value = groupsRes.data?.items || [] + relationMembers.value = membersRes.data?.items || [] + } catch (err) { + ElMessage.error('載入模組關聯資料失敗') + } finally { + relationLoading.value = false + } +} + onMounted(load) diff --git a/frontend/src/pages/admin/PermissionGroupsPage.vue b/frontend/src/pages/admin/PermissionGroupsPage.vue index 954faa8..d0e91ff 100644 --- a/frontend/src/pages/admin/PermissionGroupsPage.vue +++ b/frontend/src/pages/admin/PermissionGroupsPage.vue @@ -1,105 +1,96 @@ - - -
- Group: {{ selectedGroupKey }} -
- - - - - - - - - -
diff --git a/frontend/src/pages/permissions/PermissionAdminPage.vue b/frontend/src/pages/permissions/PermissionAdminPage.vue index f8cca8e..49bd5c1 100644 --- a/frontend/src/pages/permissions/PermissionAdminPage.vue +++ b/frontend/src/pages/permissions/PermissionAdminPage.vue @@ -27,7 +27,6 @@ - @@ -99,7 +98,6 @@ - @@ -162,7 +160,6 @@
- 查詢 @@ -210,7 +207,7 @@ const modules = ref([]) const companies = ref([]) const sites = ref([]) const members = ref([]) -const actionOptions = ['view', 'edit', 'manage', 'admin'] +const actionOptions = ['view', 'edit'] const listFilters = reactive({ keyword: '', scope_type: '' }) const listLoading = ref(false) const directPermissions = ref([]) @@ -246,9 +243,6 @@ const grantModuleOptions = computed(() => { }) const grantScopeOptions = computed(() => { - if (grantForm.scope_type === 'company') { - return companies.value.map(c => ({ value: c.company_key, label: `${c.name} (${c.company_key})` })) - } if (grantForm.scope_type === 'site') { return sites.value.map(s => ({ value: s.site_key, label: `${s.name} (${s.site_key})` })) } @@ -318,9 +312,6 @@ const revokeModuleOptions = computed(() => { }) const revokeScopeOptions = computed(() => { - if (revokeForm.scope_type === 'company') { - return companies.value.map(c => ({ value: c.company_key, label: `${c.name} (${c.company_key})` })) - } if (revokeForm.scope_type === 'site') { return sites.value.map(s => ({ value: s.site_key, label: `${s.name} (${s.site_key})` })) }