replace launcher common settings with internal launcher settings
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -1523,6 +1523,15 @@ function App() {
|
||||
return () => window.removeEventListener(authExpiredEventName, handleAuthExpired);
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleAdminPermissionsChange = () => {
|
||||
setCurrentAdmin(readCurrentAdmin());
|
||||
};
|
||||
|
||||
window.addEventListener('studio-admin-app-permissions-change', handleAdminPermissionsChange);
|
||||
return () => window.removeEventListener('studio-admin-app-permissions-change', handleAdminPermissionsChange);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (location.pathname === '/') navigate('/dashboard', { replace: true });
|
||||
}, [location.pathname, navigate]);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
export const adminAppPermissionsStorageKey = 'studio-admin-app-permissions';
|
||||
|
||||
export type CurrentAdminLike = {
|
||||
id?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
role?: string;
|
||||
appPermissions?: string[];
|
||||
};
|
||||
|
||||
type PermissionMap = Record<string, string[]>;
|
||||
|
||||
export function readCurrentAdmin(): CurrentAdminLike | null {
|
||||
try {
|
||||
const raw = window.localStorage.getItem('studio-admin');
|
||||
@@ -20,44 +17,20 @@ export function readCurrentAdmin(): CurrentAdminLike | null {
|
||||
}
|
||||
}
|
||||
|
||||
export function getAdminPermissionKey(admin: CurrentAdminLike) {
|
||||
return admin.id || admin.email || '';
|
||||
}
|
||||
|
||||
export function isOwnerAdmin(admin?: CurrentAdminLike | null) {
|
||||
return admin?.role === 'OWNER';
|
||||
}
|
||||
|
||||
export function getAdminAppPermissionMap(): PermissionMap {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(adminAppPermissionsStorageKey);
|
||||
if (!raw) return {};
|
||||
const parsed = JSON.parse(raw) as PermissionMap;
|
||||
return parsed && typeof parsed === 'object' ? parsed : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function getAdminAppPermissions(admin: CurrentAdminLike) {
|
||||
const key = getAdminPermissionKey(admin);
|
||||
const map = getAdminAppPermissionMap();
|
||||
if (key && map[key]) return map[key];
|
||||
if (admin.id && map[admin.id]) return map[admin.id];
|
||||
if (admin.email && map[admin.email]) return map[admin.email];
|
||||
return [];
|
||||
return Array.isArray(admin.appPermissions) ? admin.appPermissions : [];
|
||||
}
|
||||
|
||||
export function setAdminAppPermissions(admin: CurrentAdminLike, appIds: string[]) {
|
||||
const key = getAdminPermissionKey(admin);
|
||||
if (!key) return;
|
||||
const current = readCurrentAdmin();
|
||||
if (!current || !admin.id || current.id !== admin.id) return;
|
||||
|
||||
const nextMap = getAdminAppPermissionMap();
|
||||
const nextAppIds = [...new Set(appIds.filter(Boolean))];
|
||||
nextMap[key] = nextAppIds;
|
||||
if (admin.id) nextMap[admin.id] = nextAppIds;
|
||||
if (admin.email) nextMap[admin.email] = nextAppIds;
|
||||
window.localStorage.setItem(adminAppPermissionsStorageKey, JSON.stringify(nextMap));
|
||||
window.localStorage.setItem('studio-admin', JSON.stringify({ ...current, appPermissions: nextAppIds }));
|
||||
window.dispatchEvent(new CustomEvent('studio-admin-app-permissions-change'));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
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>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
Reference in New Issue
Block a user