admin appPermissions
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-09 09:06:55 +09:00
parent ecbf57d978
commit fd590540bc
4 changed files with 64 additions and 47 deletions
+26 -13
View File
@@ -4,16 +4,18 @@ import { DataTableCard, type DataTableColumn, type DataTableFilter } from '../..
import { useToast } from '../../components/toast';
import { Button, Modal } from '../../components/ui';
import { PageTitle } from '../../layout/AppLayout';
import { getAdminAppPermissions, isOwnerAdmin, setAdminAppPermissions } from '../../lib/admin-app-permissions';
import { isOwnerAdmin, setAdminAppPermissions } from '../../lib/admin-app-permissions';
import { listApps } from '../applications/application-api';
import type { AppWithComponents } from '../../types/application';
import {
createAdmin,
createAdminSshKey,
deleteAdminSshKey,
loadAdminAppPermissions,
loadAdminRoles,
loadAdmins,
resetAdminPassword,
updateAdminAppPermissions,
updateAdminRole,
updateAdminStatus,
type AdminAccount,
@@ -68,6 +70,7 @@ export function AdminManagementPage() {
});
const [permissionApps, setPermissionApps] = useState<AppWithComponents[]>([]);
const [permissionLoading, setPermissionLoading] = useState(false);
const [permissionSaving, setPermissionSaving] = useState(false);
const loadingVisible = useMinimumLoading(loading);
const refresh = () => {
@@ -123,14 +126,15 @@ export function AdminManagementPage() {
const openPermissionModal = (admin: AdminAccount) => {
setMenuAdmin(null);
setPermissionTarget({ admin, appIds: isOwnerAdmin(admin) ? [] : getAdminAppPermissions(admin) });
setPermissionTarget({ admin, appIds: [] });
setPermissionLoading(true);
listApps()
.then((apps) => {
Promise.all([
listApps(),
isOwnerAdmin(admin) ? Promise.resolve<string[]>([]) : loadAdminAppPermissions(admin.id)
])
.then(([apps, appIds]) => {
setPermissionApps(apps);
if (isOwnerAdmin(admin)) {
setPermissionTarget({ admin, appIds: apps.map((app) => app.id) });
}
setPermissionTarget({ admin, appIds: isOwnerAdmin(admin) ? apps.map((app) => app.id) : appIds });
})
.catch(() => {
setPermissionApps([]);
@@ -152,9 +156,18 @@ export function AdminManagementPage() {
return;
}
setAdminAppPermissions(permissionTarget.admin, permissionTarget.appIds);
setPermissionTarget({ admin: null, appIds: [] });
toast({ type: 'success', title: '앱 권한 저장', message: '관리자 앱 권한이 저장되었습니다.' });
setPermissionSaving(true);
updateAdminAppPermissions(permissionTarget.admin.id, permissionTarget.appIds)
.then((result) => {
setAdminAppPermissions(permissionTarget.admin as AdminAccount, result.appIds ?? permissionTarget.appIds);
setPermissionTarget({ admin: null, appIds: [] });
toast({ type: 'success', title: '앱 권한 저장', message: '관리자 앱 권한이 저장되었습니다.' });
refresh();
})
.catch((error) => {
toast({ type: 'error', title: '앱 권한 저장 실패', message: error instanceof Error ? error.message : '관리자 앱 권한 저장에 실패했습니다.' });
})
.finally(() => setPermissionSaving(false));
};
const submitAdmin = () => {
@@ -486,9 +499,9 @@ export function AdminManagementPage() {
className="max-w-2xl"
footer={
<>
<Button variant="outline" onClick={() => setPermissionTarget({ admin: null, appIds: [] })}>취소</Button>
<Button onClick={saveAppPermissions} disabled={permissionLoading || isOwnerAdmin(permissionTarget.admin)}>
저장
<Button variant="outline" onClick={() => setPermissionTarget({ admin: null, appIds: [] })} disabled={permissionSaving}>취소</Button>
<Button onClick={saveAppPermissions} disabled={permissionLoading || permissionSaving || isOwnerAdmin(permissionTarget.admin)}>
{permissionSaving ? '저장 중' : '저장'}
</Button>
</>
}
+24 -2
View File
@@ -20,6 +20,7 @@ export type AdminAccount = {
lastLoginAt?: string | null;
sessions?: AdminSession[];
sshKeys?: AdminSshKey[];
appPermissions?: string[];
};
export type AdminRoleOption = {
@@ -81,8 +82,17 @@ export type LauncherNotice = {
};
export async function loadAdmins() {
const response = await apiRequest<AdminAccount[] | { admins?: AdminAccount[] }>('/admins');
return Array.isArray(response) ? response : response.admins ?? [];
type RawAdminAccount = Omit<AdminAccount, 'appPermissions'> & {
appPermissions?: Array<string | { appId: string }>;
};
const response = await apiRequest<RawAdminAccount[] | { admins?: RawAdminAccount[] }>('/admins');
const admins = Array.isArray(response) ? response : response.admins ?? [];
return admins.map((admin) => ({
...admin,
appPermissions: (admin.appPermissions ?? []).map((permission) => (
typeof permission === 'string' ? permission : permission.appId
))
}));
}
export function loadAdminRoles() {
@@ -138,6 +148,18 @@ export function deleteAdminSshKey(adminId: string, keyId: string) {
});
}
export async function loadAdminAppPermissions(adminId: string) {
const response = await apiRequest<{ appIds?: string[] }>(`/admins/${adminId}/app-permissions`);
return response.appIds ?? [];
}
export function updateAdminAppPermissions(adminId: string, appIds: string[]) {
return apiRequest<{ appIds?: string[] }>(`/admins/${adminId}/app-permissions`, {
method: 'PUT',
body: JSON.stringify({ appIds })
});
}
export async function loadUserSessions() {
const response = await apiRequest<{ sessions?: UserSession[] }>('/users/sessions');
return (response.sessions ?? []).map((session) => ({