From 20ca8d24dcf7d12e7a44f8105032c94fd38abaa2 Mon Sep 17 00:00:00 2001 From: chyoo Date: Thu, 2 Jul 2026 13:16:48 +0900 Subject: [PATCH] add launcher user app permission management --- src/pages/operations/LicensesPage.tsx | 130 ++++++++++++++++++++++++- src/pages/operations/operations-api.ts | 29 ++++++ 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/pages/operations/LicensesPage.tsx b/src/pages/operations/LicensesPage.tsx index 91cbe3e..6b6cfe5 100644 --- a/src/pages/operations/LicensesPage.tsx +++ b/src/pages/operations/LicensesPage.tsx @@ -1,4 +1,4 @@ -import { MoreHorizontal, Pencil, Plus, Trash2 } from 'lucide-react'; +import { MoreHorizontal, Pencil, Plus, ShieldCheck, Trash2 } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; import { DataTableCard, type DataTableColumn, type DataTableFilter } from '../../components/data-table'; import { Button, Card, Modal } from '../../components/ui'; @@ -10,9 +10,12 @@ import { assignUserLicense, loadLicensePlans, loadLicenseUsers, + loadLauncherUserAppPermissions, revokeUserLicense, + updateLauncherUserAppPermissions, updateUserLicense, type AuthLicenseUser, + type LauncherUserAppPermission, type LicensePlan } from './operations-api'; @@ -81,6 +84,9 @@ export function LicensesPage() { const [assignOpen, setAssignOpen] = useState(false); const [editingRow, setEditingRow] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); + const [appPermissionTarget, setAppPermissionTarget] = useState(null); + const [appPermissions, setAppPermissions] = useState([]); + const [appPermissionLoading, setAppPermissionLoading] = useState(false); const [menuRow, setMenuRow] = useState<{ id: string; x: number; y: number } | null>(null); const [saving, setSaving] = useState(false); const [assignForm, setAssignForm] = useState({ @@ -217,6 +223,59 @@ export function LicensesPage() { } }; + const openAppPermissionModal = async (row: LicenseAssignmentRow) => { + setMenuRow(null); + setAppPermissionTarget(row); + setAppPermissions([]); + setAppPermissionLoading(true); + try { + setAppPermissions(await loadLauncherUserAppPermissions(row.userId)); + } catch (error) { + toast({ type: 'error', message: error instanceof Error ? error.message : '앱 권한을 불러오지 못했습니다.' }); + } finally { + setAppPermissionLoading(false); + } + }; + + const closeAppPermissionModal = () => { + setAppPermissionTarget(null); + setAppPermissions([]); + }; + + const toggleAppPermission = (appKey: string, field: 'canView' | 'canLaunch') => { + setAppPermissions((prev) => prev.map((permission) => { + if (permission.appKey !== appKey) return permission; + if (field === 'canView') { + const canView = !permission.canView; + return { ...permission, canView, canLaunch: canView ? permission.canLaunch : false }; + } + const canLaunch = !permission.canLaunch; + return { ...permission, canView: canLaunch ? true : permission.canView, canLaunch }; + })); + }; + + const saveAppPermissions = async () => { + if (!appPermissionTarget) return; + setSaving(true); + try { + const result = await updateLauncherUserAppPermissions( + appPermissionTarget.userId, + appPermissions.map((permission) => ({ + appKey: permission.appKey, + canView: permission.canView, + canLaunch: permission.canView && permission.canLaunch + })) + ); + setAppPermissions(result.permissions ?? []); + toast({ type: 'success', message: '앱 권한을 저장했습니다.' }); + closeAppPermissionModal(); + } catch (error) { + toast({ type: 'error', message: error instanceof Error ? error.message : '앱 권한 저장에 실패했습니다.' }); + } finally { + setSaving(false); + } + }; + const columns = useMemo>>( () => [ { @@ -343,6 +402,13 @@ export function LicensesPage() { > 수정 + + + + } + > +
+
+

{appPermissionTarget?.user ?? '-'}

+

{appPermissionTarget?.name || '-'}

+
+
+ {appPermissionLoading ? ( +
앱 권한을 불러오는 중입니다.
+ ) : appPermissions.length === 0 ? ( +
런처에 연결된 앱이 없습니다.
+ ) : ( + appPermissions.map((permission) => ( +
+
+

{permission.displayName}

+

{permission.appKey}

+
+
+ + +
+
+ )) + )} +
+
+ ); diff --git a/src/pages/operations/operations-api.ts b/src/pages/operations/operations-api.ts index e1f9864..54778dd 100644 --- a/src/pages/operations/operations-api.ts +++ b/src/pages/operations/operations-api.ts @@ -269,3 +269,32 @@ export function revokeUserLicense(userLicenseId: string) { method: 'DELETE' }); } + +export type LauncherUserAppPermission = { + appId: string; + appKey: string; + displayName: string; + description?: string | null; + sortOrder: number; + isEnabled: boolean; + permissionId?: string | null; + canView: boolean; + canLaunch: boolean; + assignedAt?: string | null; +}; + +export async function loadLauncherUserAppPermissions(userId: string) { + const response = await apiRequest<{ permissions?: LauncherUserAppPermission[] }>(`/launcher/users/${userId}/app-permissions`); + return response.permissions ?? []; +} + +export function updateLauncherUserAppPermissions(userId: string, permissions: Array<{ + appKey: string; + canView: boolean; + canLaunch: boolean; +}>) { + return apiRequest<{ permissions?: LauncherUserAppPermission[] }>(`/launcher/users/${userId}/app-permissions`, { + method: 'PUT', + body: JSON.stringify({ permissions }) + }); +}