diff --git a/backend/app/api/admin_catalog.py b/backend/app/api/admin_catalog.py index 7eb71e1..19fb4eb 100644 --- a/backend/app/api/admin_catalog.py +++ b/backend/app/api/admin_catalog.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.orm import Session +from app.core.config import get_settings from app.db.session import get_db from app.models.api_client import ApiClient from app.repositories.companies_repo import CompaniesRepository @@ -12,18 +13,26 @@ from app.repositories.users_repo import UsersRepository from app.schemas.catalog import ( CompanyCreateRequest, CompanyItem, + CompanyUpdateRequest, MemberItem, + MemberUpdateRequest, + MemberUpsertRequest, ModuleCreateRequest, ModuleItem, + ModuleUpdateRequest, PermissionGroupCreateRequest, PermissionGroupItem, + PermissionGroupUpdateRequest, SiteCreateRequest, SiteItem, + SiteUpdateRequest, SystemCreateRequest, SystemItem, + SystemUpdateRequest, ) from app.schemas.permissions import PermissionGrantRequest, PermissionRevokeRequest from app.security.api_client_auth import require_api_client +from app.services.authentik_admin_service import AuthentikAdminService router = APIRouter(prefix="/admin", tags=["admin"]) @@ -57,6 +66,26 @@ 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 _sync_member_to_authentik( + *, + authentik_sub: str, + email: str | None, + display_name: str | None, + is_active: bool, +) -> dict[str, str | int]: + if not email: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="email_required_for_authentik_sync") + settings = get_settings() + service = AuthentikAdminService(settings=settings) + result = service.ensure_user( + sub=authentik_sub, + email=email, + display_name=display_name, + is_active=is_active, + ) + return {"authentik_user_id": result.user_id, "sync_action": result.action} + + @router.get("/systems") def list_systems( _: ApiClient = Depends(require_api_client), @@ -82,6 +111,21 @@ def create_system( return SystemItem(id=row.id, system_key=row.system_key, name=row.name, status=row.status) +@router.patch("/systems/{system_key}", response_model=SystemItem) +def update_system( + system_key: str, + payload: SystemUpdateRequest, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> SystemItem: + repo = SystemsRepository(db) + row = repo.get_by_key(system_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="system_not_found") + row = repo.update(row, name=payload.name, status=payload.status) + return SystemItem(id=row.id, system_key=row.system_key, name=row.name, status=row.status) + + @router.get("/modules") def list_modules( _: ApiClient = Depends(require_api_client), @@ -128,6 +172,22 @@ def create_module( return ModuleItem(id=row.id, system_key=payload.system_key, module_key=row.module_key, name=row.name, status=row.status) +@router.patch("/modules/{module_key}") +def update_module( + module_key: str, + payload: ModuleUpdateRequest, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> ModuleItem: + modules_repo = ModulesRepository(db) + row = modules_repo.get_by_key(module_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="module_not_found") + row = modules_repo.update(row, name=payload.name, status=payload.status) + system_key = row.module_key.split(".", 1)[0] if "." in row.module_key else None + return ModuleItem(id=row.id, system_key=system_key, module_key=row.module_key, name=row.name, status=row.status) + + @router.get("/companies") def list_companies( _: ApiClient = Depends(require_api_client), @@ -154,6 +214,21 @@ def create_company( return CompanyItem(id=row.id, company_key=row.company_key, name=row.name, status=row.status) +@router.patch("/companies/{company_key}", response_model=CompanyItem) +def update_company( + company_key: str, + payload: CompanyUpdateRequest, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> CompanyItem: + repo = CompaniesRepository(db) + row = repo.get_by_key(company_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="company_not_found") + row = repo.update(row, name=payload.name, status=payload.status) + return CompanyItem(id=row.id, company_key=row.company_key, name=row.name, status=row.status) + + @router.get("/sites") def list_sites( _: ApiClient = Depends(require_api_client), @@ -210,6 +285,33 @@ def create_site( return SiteItem(id=row.id, site_key=row.site_key, company_key=payload.company_key, name=row.name, status=row.status) +@router.patch("/sites/{site_key}", response_model=SiteItem) +def update_site( + site_key: str, + payload: SiteUpdateRequest, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> SiteItem: + companies_repo = CompaniesRepository(db) + sites_repo = SitesRepository(db) + row = sites_repo.get_by_key(site_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="site_not_found") + company_id = None + company_key = None + if payload.company_key is not None: + company = companies_repo.get_by_key(payload.company_key) + if not company: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="company_not_found") + company_id = company.id + company_key = company.company_key + row = sites_repo.update(row, company_id=company_id, name=payload.name, status=payload.status) + if company_key is None: + current_company = companies_repo.get_by_id(row.company_id) + company_key = current_company.company_key if current_company else "" + return SiteItem(id=row.id, site_key=row.site_key, company_key=company_key, name=row.name, status=row.status) + + @router.get("/members") def list_members( _: ApiClient = Depends(require_api_client), @@ -223,6 +325,80 @@ def list_members( return {"items": [MemberItem(id=i.id, authentik_sub=i.authentik_sub, email=i.email, display_name=i.display_name, is_active=i.is_active).model_dump() for i in items], "total": total, "limit": limit, "offset": offset} +@router.post("/members/upsert", response_model=MemberItem) +def upsert_member( + payload: MemberUpsertRequest, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> MemberItem: + users_repo = UsersRepository(db) + authentik_user_id = None + if payload.sync_to_authentik: + sync = _sync_member_to_authentik( + authentik_sub=payload.authentik_sub, + email=payload.email, + display_name=payload.display_name, + is_active=payload.is_active, + ) + authentik_user_id = int(sync["authentik_user_id"]) + row = users_repo.upsert_by_sub( + authentik_sub=payload.authentik_sub, + email=payload.email, + display_name=payload.display_name, + is_active=payload.is_active, + authentik_user_id=authentik_user_id, + ) + return MemberItem( + id=row.id, + authentik_sub=row.authentik_sub, + email=row.email, + display_name=row.display_name, + is_active=row.is_active, + ) + + +@router.patch("/members/{authentik_sub}", response_model=MemberItem) +def update_member( + authentik_sub: str, + payload: MemberUpdateRequest, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> MemberItem: + users_repo = UsersRepository(db) + row = users_repo.get_by_sub(authentik_sub) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found") + + next_email = payload.email if payload.email is not None else row.email + next_display_name = payload.display_name if payload.display_name is not None else row.display_name + next_is_active = payload.is_active if payload.is_active is not None else row.is_active + + authentik_user_id = row.authentik_user_id + if payload.sync_to_authentik: + sync = _sync_member_to_authentik( + authentik_sub=row.authentik_sub, + email=next_email, + display_name=next_display_name, + is_active=next_is_active, + ) + authentik_user_id = int(sync["authentik_user_id"]) + + row = users_repo.upsert_by_sub( + authentik_sub=row.authentik_sub, + email=next_email, + display_name=next_display_name, + is_active=next_is_active, + authentik_user_id=authentik_user_id, + ) + return MemberItem( + id=row.id, + authentik_sub=row.authentik_sub, + email=row.email, + display_name=row.display_name, + is_active=row.is_active, + ) + + @router.get("/permission-groups") def list_permission_groups( _: ApiClient = Depends(require_api_client), @@ -248,6 +424,21 @@ def create_permission_group( return PermissionGroupItem(id=row.id, group_key=row.group_key, name=row.name, status=row.status) +@router.patch("/permission-groups/{group_key}", response_model=PermissionGroupItem) +def update_permission_group( + group_key: str, + payload: PermissionGroupUpdateRequest, + _: ApiClient = Depends(require_api_client), + db: Session = Depends(get_db), +) -> PermissionGroupItem: + repo = PermissionGroupsRepository(db) + row = repo.get_by_key(group_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found") + row = repo.update(row, name=payload.name, status=payload.status) + return PermissionGroupItem(id=row.id, group_key=row.group_key, name=row.name, status=row.status) + + @router.post("/permission-groups/{group_key}/members/{authentik_sub}") def add_group_member( group_key: str, diff --git a/backend/app/repositories/companies_repo.py b/backend/app/repositories/companies_repo.py index 47628f2..184ff21 100644 --- a/backend/app/repositories/companies_repo.py +++ b/backend/app/repositories/companies_repo.py @@ -33,3 +33,12 @@ class CompaniesRepository: self.db.commit() self.db.refresh(item) return item + + def update(self, item: Company, *, name: str | None = None, status: str | None = None) -> Company: + if name is not None: + item.name = name + if status is not None: + item.status = status + self.db.commit() + self.db.refresh(item) + return item diff --git a/backend/app/repositories/modules_repo.py b/backend/app/repositories/modules_repo.py index b457266..2c5e486 100644 --- a/backend/app/repositories/modules_repo.py +++ b/backend/app/repositories/modules_repo.py @@ -24,3 +24,12 @@ class ModulesRepository: self.db.commit() self.db.refresh(item) return item + + def update(self, item: Module, *, name: str | None = None, status: str | None = None) -> Module: + if name is not None: + item.name = name + if status is not None: + item.status = status + self.db.commit() + self.db.refresh(item) + return item diff --git a/backend/app/repositories/permission_groups_repo.py b/backend/app/repositories/permission_groups_repo.py index a1fa6e4..b67dd8c 100644 --- a/backend/app/repositories/permission_groups_repo.py +++ b/backend/app/repositories/permission_groups_repo.py @@ -28,6 +28,15 @@ class PermissionGroupsRepository: self.db.refresh(item) return item + def update(self, item: PermissionGroup, *, name: str | None = None, status: str | None = None) -> PermissionGroup: + if name is not None: + item.name = name + if status is not None: + item.status = status + self.db.commit() + self.db.refresh(item) + return item + def add_member_if_not_exists(self, group_id: str, authentik_sub: str) -> PermissionGroupMember: existing = self.db.scalar( select(PermissionGroupMember).where( diff --git a/backend/app/repositories/sites_repo.py b/backend/app/repositories/sites_repo.py index d3e58c4..f17cc5b 100644 --- a/backend/app/repositories/sites_repo.py +++ b/backend/app/repositories/sites_repo.py @@ -38,3 +38,21 @@ class SitesRepository: self.db.commit() self.db.refresh(item) return item + + def update( + self, + item: Site, + *, + company_id: str | None = None, + name: str | None = None, + status: str | None = None, + ) -> Site: + if company_id is not None: + item.company_id = company_id + if name is not None: + item.name = name + if status is not None: + item.status = status + self.db.commit() + self.db.refresh(item) + return item diff --git a/backend/app/repositories/systems_repo.py b/backend/app/repositories/systems_repo.py index 0464fea..d265822 100644 --- a/backend/app/repositories/systems_repo.py +++ b/backend/app/repositories/systems_repo.py @@ -31,3 +31,12 @@ class SystemsRepository: self.db.commit() self.db.refresh(item) return item + + def update(self, item: System, *, name: str | None = None, status: str | None = None) -> System: + if name is not None: + item.name = name + if status is not None: + item.status = status + self.db.commit() + self.db.refresh(item) + return item diff --git a/backend/app/schemas/catalog.py b/backend/app/schemas/catalog.py index 90c0d98..637f8e8 100644 --- a/backend/app/schemas/catalog.py +++ b/backend/app/schemas/catalog.py @@ -7,6 +7,11 @@ class SystemCreateRequest(BaseModel): status: str = "active" +class SystemUpdateRequest(BaseModel): + name: str | None = None + status: str | None = None + + class SystemItem(BaseModel): id: str system_key: str @@ -21,6 +26,11 @@ class ModuleCreateRequest(BaseModel): status: str = "active" +class ModuleUpdateRequest(BaseModel): + name: str | None = None + status: str | None = None + + class ModuleItem(BaseModel): id: str system_key: str | None = None @@ -35,6 +45,11 @@ class CompanyCreateRequest(BaseModel): status: str = "active" +class CompanyUpdateRequest(BaseModel): + name: str | None = None + status: str | None = None + + class CompanyItem(BaseModel): id: str company_key: str @@ -49,6 +64,12 @@ class SiteCreateRequest(BaseModel): status: str = "active" +class SiteUpdateRequest(BaseModel): + company_key: str | None = None + name: str | None = None + status: str | None = None + + class SiteItem(BaseModel): id: str site_key: str @@ -65,6 +86,21 @@ class MemberItem(BaseModel): is_active: bool +class MemberUpsertRequest(BaseModel): + authentik_sub: str + email: str | None = None + display_name: str | None = None + is_active: bool = True + sync_to_authentik: bool = True + + +class MemberUpdateRequest(BaseModel): + email: str | None = None + display_name: str | None = None + is_active: bool | None = None + sync_to_authentik: bool = True + + class ListResponse(BaseModel): items: list total: int @@ -78,6 +114,11 @@ class PermissionGroupCreateRequest(BaseModel): status: str = "active" +class PermissionGroupUpdateRequest(BaseModel): + name: str | None = None + status: str | None = None + + class PermissionGroupItem(BaseModel): id: str group_key: str diff --git a/frontend/src/api/companies.js b/frontend/src/api/companies.js index c62607a..9411dd3 100644 --- a/frontend/src/api/companies.js +++ b/frontend/src/api/companies.js @@ -2,3 +2,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) diff --git a/frontend/src/api/members.js b/frontend/src/api/members.js index 7a10123..99c1c2f 100644 --- a/frontend/src/api/members.js +++ b/frontend/src/api/members.js @@ -1,3 +1,5 @@ import { adminHttp } from './http' export const getMembers = () => adminHttp.get('/admin/members') +export const upsertMember = (data) => adminHttp.post('/admin/members/upsert', data) +export const updateMember = (authentikSub, data) => adminHttp.patch(`/admin/members/${authentikSub}`, data) diff --git a/frontend/src/api/modules.js b/frontend/src/api/modules.js index f54f398..3d0ba34 100644 --- a/frontend/src/api/modules.js +++ b/frontend/src/api/modules.js @@ -2,3 +2,4 @@ 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) diff --git a/frontend/src/api/permission-groups.js b/frontend/src/api/permission-groups.js index 3ebf8ea..a78e317 100644 --- a/frontend/src/api/permission-groups.js +++ b/frontend/src/api/permission-groups.js @@ -2,6 +2,7 @@ import { adminHttp } from './http' 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 addMemberToGroup = (groupKey, authentikSub) => adminHttp.post(`/admin/permission-groups/${groupKey}/members/${authentikSub}`) diff --git a/frontend/src/api/sites.js b/frontend/src/api/sites.js index 674bddf..7cfa055 100644 --- a/frontend/src/api/sites.js +++ b/frontend/src/api/sites.js @@ -2,3 +2,4 @@ import { adminHttp } from './http' export const getSites = () => adminHttp.get('/admin/sites') export const createSite = (data) => adminHttp.post('/admin/sites', data) +export const updateSite = (siteKey, data) => adminHttp.patch(`/admin/sites/${siteKey}`, data) diff --git a/frontend/src/api/systems.js b/frontend/src/api/systems.js index 6d90cd7..90cac1a 100644 --- a/frontend/src/api/systems.js +++ b/frontend/src/api/systems.js @@ -2,3 +2,4 @@ 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) diff --git a/frontend/src/pages/admin/CompaniesPage.vue b/frontend/src/pages/admin/CompaniesPage.vue index 167ed8f..5eca360 100644 --- a/frontend/src/pages/admin/CompaniesPage.vue +++ b/frontend/src/pages/admin/CompaniesPage.vue @@ -5,38 +5,48 @@ 新增公司 - - + - - + + + + + + - - - - - - - + + + + + + + + + + + + + + + + @@ -44,7 +54,7 @@ import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' import { Plus } from '@element-plus/icons-vue' -import { getCompanies, createCompany } from '@/api/companies' +import { getCompanies, createCompany, updateCompany } from '@/api/companies' const companies = ref([]) const loading = ref(false) @@ -52,9 +62,12 @@ const error = ref(false) const errorMsg = ref('') const showDialog = ref(false) const submitting = ref(false) +const showEditDialog = ref(false) +const savingEdit = ref(false) const formRef = ref() const form = ref({ company_key: '', name: '' }) +const editForm = ref({ company_key: '', name: '', status: 'active' }) const rules = { company_key: [{ required: true, message: '請輸入 Company Key', trigger: 'blur' }], name: [{ required: true, message: '請輸入名稱', trigger: 'blur' }] @@ -80,6 +93,15 @@ function resetForm() { form.value = { company_key: '', name: '' } } +function openEdit(row) { + editForm.value = { company_key: row.company_key, name: row.name, status: row.status || 'active' } + showEditDialog.value = true +} + +function resetEditForm() { + editForm.value = { company_key: '', name: '', status: 'active' } +} + async function handleCreate() { const valid = await formRef.value.validate().catch(() => false) if (!valid) return @@ -91,11 +113,25 @@ async function handleCreate() { resetForm() await load() } catch (err) { - ElMessage.error('新增失敗,請稍後再試') + ElMessage.error('新增失敗') } finally { submitting.value = false } } +async function handleEdit() { + savingEdit.value = true + try { + await updateCompany(editForm.value.company_key, { name: editForm.value.name, status: editForm.value.status }) + ElMessage.success('更新成功') + showEditDialog.value = false + await load() + } catch (err) { + ElMessage.error('更新失敗') + } finally { + savingEdit.value = false + } +} + onMounted(load) diff --git a/frontend/src/pages/admin/MembersPage.vue b/frontend/src/pages/admin/MembersPage.vue index ac0f18b..f98a0ef 100644 --- a/frontend/src/pages/admin/MembersPage.vue +++ b/frontend/src/pages/admin/MembersPage.vue @@ -2,39 +2,95 @@

會員列表

- 重新整理 +
+ 新增會員 + 重新整理 +
- - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/frontend/src/pages/admin/ModulesPage.vue b/frontend/src/pages/admin/ModulesPage.vue index 379952b..8a5aff0 100644 --- a/frontend/src/pages/admin/ModulesPage.vue +++ b/frontend/src/pages/admin/ModulesPage.vue @@ -18,12 +18,17 @@ - - + + + + + + - @@ -41,6 +46,27 @@ 確認 + + + + + + + + + + + + + + + + + + @@ -48,7 +74,7 @@ import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' import { Plus } from '@element-plus/icons-vue' -import { getModules, createModule } from '@/api/modules' +import { getModules, createModule, updateModule } from '@/api/modules' const modules = ref([]) const loading = ref(false) @@ -57,8 +83,11 @@ const errorMsg = ref('') const showDialog = ref(false) const submitting = ref(false) const formRef = ref() +const showEditDialog = ref(false) +const savingEdit = ref(false) const form = ref({ system_key: '', module_key: '', name: '' }) +const editForm = ref({ module_key: '', name: '', status: 'active' }) const rules = { system_key: [{ required: true, message: '請輸入 System Key', trigger: 'blur' }], module_key: [{ required: true, message: '請輸入 Module Key', trigger: 'blur' }], @@ -85,6 +114,19 @@ function resetForm() { form.value = { system_key: '', module_key: '', name: '' } } +function openEdit(row) { + editForm.value = { + module_key: row.module_key, + name: row.name, + status: row.status || 'active' + } + showEditDialog.value = true +} + +function resetEditForm() { + editForm.value = { module_key: '', name: '', status: 'active' } +} + async function handleCreate() { const valid = await formRef.value.validate().catch(() => false) if (!valid) return @@ -102,5 +144,22 @@ async function handleCreate() { } } +async function handleEdit() { + savingEdit.value = true + try { + await updateModule(editForm.value.module_key, { + name: editForm.value.name, + status: editForm.value.status + }) + ElMessage.success('更新成功') + showEditDialog.value = false + await load() + } catch (err) { + ElMessage.error('更新失敗') + } finally { + savingEdit.value = false + } +} + onMounted(load) diff --git a/frontend/src/pages/admin/PermissionGroupsPage.vue b/frontend/src/pages/admin/PermissionGroupsPage.vue index c6b1f1f..39cd342 100644 --- a/frontend/src/pages/admin/PermissionGroupsPage.vue +++ b/frontend/src/pages/admin/PermissionGroupsPage.vue @@ -16,6 +16,12 @@ + + + + @@ -60,16 +66,34 @@ - + + + - + + + - + + + - + + + 確認 + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/pages/admin/SitesPage.vue b/frontend/src/pages/admin/SitesPage.vue index ce939d9..3a5b3cc 100644 --- a/frontend/src/pages/admin/SitesPage.vue +++ b/frontend/src/pages/admin/SitesPage.vue @@ -5,42 +5,59 @@ 新增站台 - - + - - + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -48,29 +65,39 @@ import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' import { Plus } from '@element-plus/icons-vue' -import { getSites, createSite } from '@/api/sites' +import { getSites, createSite, updateSite } from '@/api/sites' +import { getCompanies } from '@/api/companies' const sites = ref([]) +const companies = ref([]) const loading = ref(false) const error = ref(false) const errorMsg = ref('') const showDialog = ref(false) const submitting = ref(false) +const showEditDialog = ref(false) +const savingEdit = ref(false) const formRef = ref() const form = ref({ site_key: '', company_key: '', name: '' }) +const editForm = ref({ site_key: '', company_key: '', name: '', status: 'active' }) const rules = { site_key: [{ required: true, message: '請輸入 Site Key', trigger: 'blur' }], - company_key: [{ required: true, message: '請輸入 Company Key', trigger: 'blur' }], + company_key: [{ required: true, message: '請選擇公司', trigger: 'change' }], name: [{ required: true, message: '請輸入名稱', trigger: 'blur' }] } +async function loadCompanies() { + const res = await getCompanies() + companies.value = res.data?.items || [] +} + async function load() { loading.value = true error.value = false try { - const res = await getSites() - sites.value = res.data?.items || [] + const [sitesRes] = await Promise.all([getSites(), loadCompanies()]) + sites.value = sitesRes.data?.items || [] } catch (err) { error.value = true errorMsg.value = err.response?.status === 422 @@ -85,6 +112,20 @@ function resetForm() { form.value = { site_key: '', company_key: '', name: '' } } +function openEdit(row) { + editForm.value = { + site_key: row.site_key, + company_key: row.company_key, + name: row.name, + status: row.status || 'active' + } + showEditDialog.value = true +} + +function resetEditForm() { + editForm.value = { site_key: '', company_key: '', name: '', status: 'active' } +} + async function handleCreate() { const valid = await formRef.value.validate().catch(() => false) if (!valid) return @@ -96,11 +137,29 @@ async function handleCreate() { resetForm() await load() } catch (err) { - ElMessage.error('新增失敗,請稍後再試') + ElMessage.error('新增失敗') } finally { submitting.value = false } } +async function handleEdit() { + savingEdit.value = true + try { + await updateSite(editForm.value.site_key, { + company_key: editForm.value.company_key, + name: editForm.value.name, + status: editForm.value.status + }) + ElMessage.success('更新成功') + showEditDialog.value = false + await load() + } catch (err) { + ElMessage.error('更新失敗') + } finally { + savingEdit.value = false + } +} + onMounted(load) diff --git a/frontend/src/pages/admin/SystemsPage.vue b/frontend/src/pages/admin/SystemsPage.vue index d6755e6..eb95512 100644 --- a/frontend/src/pages/admin/SystemsPage.vue +++ b/frontend/src/pages/admin/SystemsPage.vue @@ -20,9 +20,14 @@ + + + + - @@ -37,6 +42,27 @@ 確認 + + + + + + + + + + + + + + + + + + @@ -44,7 +70,7 @@ import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' import { Plus } from '@element-plus/icons-vue' -import { getSystems, createSystem } from '@/api/systems' +import { getSystems, createSystem, updateSystem } from '@/api/systems' const systems = ref([]) const loading = ref(false) @@ -53,8 +79,11 @@ const errorMsg = ref('') const showDialog = ref(false) const submitting = ref(false) const formRef = ref() +const showEditDialog = ref(false) +const savingEdit = ref(false) const form = ref({ system_key: '', name: '' }) +const editForm = ref({ system_key: '', name: '', status: 'active' }) const rules = { system_key: [{ required: true, message: '請輸入 System Key', trigger: 'blur' }], name: [{ required: true, message: '請輸入名稱', trigger: 'blur' }] @@ -80,6 +109,19 @@ function resetForm() { form.value = { system_key: '', name: '' } } +function openEdit(row) { + editForm.value = { + system_key: row.system_key, + name: row.name, + status: row.status || 'active' + } + showEditDialog.value = true +} + +function resetEditForm() { + editForm.value = { system_key: '', name: '', status: 'active' } +} + async function handleCreate() { const valid = await formRef.value.validate().catch(() => false) if (!valid) return @@ -97,5 +139,22 @@ async function handleCreate() { } } +async function handleEdit() { + savingEdit.value = true + try { + await updateSystem(editForm.value.system_key, { + name: editForm.value.name, + status: editForm.value.status + }) + ElMessage.success('更新成功') + showEditDialog.value = false + await load() + } catch (err) { + ElMessage.error('更新失敗') + } finally { + savingEdit.value = false + } +} + onMounted(load) diff --git a/frontend/src/pages/permissions/PermissionAdminPage.vue b/frontend/src/pages/permissions/PermissionAdminPage.vue index 19d16ef..1710cfd 100644 --- a/frontend/src/pages/permissions/PermissionAdminPage.vue +++ b/frontend/src/pages/permissions/PermissionAdminPage.vue @@ -15,7 +15,9 @@ @submit.prevent="handleGrant" > - + + + @@ -30,16 +32,24 @@ - + + + - + + + - + + + - + + + - + + + @@ -92,16 +104,24 @@ - + + + - + + + - + + + - + + +