Compare commits

..

93 Commits

Author SHA1 Message Date
chyoo 5a814716b7 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-15 16:00:57 +09:00
chyoo 37e0ac34c6 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-15 14:10:06 +09:00
chyoo 23e3d6e480 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 15:25:12 +09:00
chyoo 0d216e2f03 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 15:08:35 +09:00
chyoo 3a46232d4f merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 13:36:16 +09:00
chyoo d37283093a merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 13:15:13 +09:00
chyoo d897cba32c merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 11:52:04 +09:00
chyoo b41941d425 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 10:45:07 +09:00
chyoo d87c92ff71 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-13 17:15:05 +09:00
chyoo ddf3aa9784 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-13 16:55:44 +09:00
chyoo e755f245f3 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 14:21:43 +09:00
chyoo 917b9b6071 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 13:48:03 +09:00
chyoo 35fe3c94b1 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 13:44:44 +09:00
chyoo 813fef4318 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 13:40:07 +09:00
chyoo af5fd60937 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 13:14:26 +09:00
chyoo f895fa8edc merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 11:21:20 +09:00
chyoo 01c82ab17c merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 11:11:29 +09:00
chyoo 994520b754 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 10:49:46 +09:00
chyoo 353bf8ea73 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 10:43:00 +09:00
chyoo a0cc8b9772 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 15:47:27 +09:00
chyoo c109f344d2 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 15:43:54 +09:00
chyoo 32412d922a merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 15:37:47 +09:00
chyoo 4afc3fc05d merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 15:33:33 +09:00
chyoo bf39f82698 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 15:10:50 +09:00
chyoo 384c66687e merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 15:05:28 +09:00
chyoo a2952a44a7 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 14:53:43 +09:00
chyoo dc683fce42 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 14:32:02 +09:00
chyoo 2a540a48b0 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 14:26:47 +09:00
chyoo cad561dfd0 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 13:56:27 +09:00
chyoo dd2fc93a59 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 13:50:25 +09:00
chyoo e1a5fd54db merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 13:23:44 +09:00
chyoo 6c14203e8f merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 13:21:39 +09:00
chyoo be3f36f6d9 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 13:09:39 +09:00
chyoo f510b1957e merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 12:54:07 +09:00
chyoo 5bb42621d0 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:55:56 +09:00
chyoo e3ae6e3f43 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:27:42 +09:00
chyoo 556f5866a7 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:20:20 +09:00
chyoo c7a6693ce4 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:05:04 +09:00
chyoo 15d993555f merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:01:36 +09:00
chyoo 39b55ad479 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 10:53:51 +09:00
chyoo 1b49256af6 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 10:22:15 +09:00
chyoo 81fc8f842a merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 10:16:30 +09:00
chyoo 44c6c08a02 replace launcher common settings with internal launcher settings
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 09:46:39 +09:00
chyoo 38a13f29fd replace launcher common settings with internal launcher settings
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 09:07:00 +09:00
chyoo 840fa0a604 replace launcher common settings with internal launcher settings
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 18:13:59 +09:00
chyoo c594c1cb85 replace launcher common settings with internal launcher settings
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 18:08:09 +09:00
chyoo e4b968e5dd replace launcher common settings with internal launcher settings
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 18:02:13 +09:00
chyoo 48b81dd5cf replace launcher common settings with internal launcher settings
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 17:51:20 +09:00
chyoo 3b471245c7 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 17:13:46 +09:00
chyoo 5ebab95a8b merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 16:39:15 +09:00
chyoo 8acbb760c8 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 15:35:57 +09:00
chyoo 8d37cf86a2 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 15:24:12 +09:00
chyoo 310daaa424 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 14:40:56 +09:00
chyoo d08a1a1a79 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 11:05:02 +09:00
chyoo c4c2a845ea merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 17:42:21 +09:00
chyoo e1d9d803d5 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 17:08:33 +09:00
chyoo 79e2e8443d merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 15:56:22 +09:00
chyoo 86e4330066 merge develop into release
ci/woodpecker/push/woodpecker Pipeline failed
2026-07-07 15:51:55 +09:00
chyoo 5ec75d68ab merge develop into release
ci/woodpecker/push/woodpecker Pipeline failed
2026-07-07 15:47:59 +09:00
chyoo 5bae18678c merge develop into release
ci/woodpecker/push/woodpecker Pipeline failed
2026-07-07 15:32:59 +09:00
chyoo 641166ba27 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 11:56:33 +09:00
chyoo e6bbaf4971 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 10:20:31 +09:00
chyoo 039a2ca285 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 10:18:14 +09:00
chyoo c345dade10 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 10:15:43 +09:00
chyoo 50e9b1d5ee merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 09:56:33 +09:00
chyoo e08ae4639b merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 09:51:28 +09:00
chyoo 3bb54af459 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 09:43:22 +09:00
chyoo 99f362fda1 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 09:29:49 +09:00
chyoo 622fe6e4b3 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 09:24:37 +09:00
chyoo bbda7887ee merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 09:20:10 +09:00
chyoo e2bb9075bd merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 09:05:21 +09:00
chyoo f6284aef79 merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 18:30:40 +09:00
chyoo a9c7a3654b Merge remote-tracking branch 'origin/develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 17:50:00 +09:00
chyoo 8c7c26e9c0 Merge remote-tracking branch 'origin/develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 17:15:57 +09:00
chyoo 070426620e Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 16:32:07 +09:00
chyoo 7e56b93f01 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 14:22:24 +09:00
chyoo 369de72e98 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 10:50:48 +09:00
chyoo 052b87e4ff Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 10:47:48 +09:00
chyoo 20d5ccef00 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 10:25:15 +09:00
chyoo b2cfd84f10 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-06 10:03:57 +09:00
chyoo 6da6e2a980 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 17:26:46 +09:00
chyoo 1118ff5663 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 17:23:46 +09:00
chyoo d529aa6f19 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 17:18:55 +09:00
chyoo e55d2f536a Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 17:00:38 +09:00
chyoo f6d9c8d0bb Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 16:30:16 +09:00
chyoo b6d1ba743b Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 16:08:04 +09:00
chyoo d5516c6cf8 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 15:33:32 +09:00
chyoo 92c0cf414b Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 15:18:14 +09:00
chyoo 718ce8d09f retry woodpecker release deploy
ci/woodpecker/push/woodpecker Pipeline failed
2026-07-03 14:58:48 +09:00
chyoo a9a9024830 Merge branch 'develop' into release 2026-07-03 14:56:01 +09:00
chyoo f7dfce5918 Merge branch 'develop' into release
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-03 14:52:00 +09:00
chyoo c6d3ff8f5c test woodpecker release deploy 2026-07-03 14:47:43 +09:00
chyoo ded18b44e5 test woodpecker release deploy 2026-07-03 14:46:01 +09:00
8 changed files with 10 additions and 132 deletions
+5 -50
View File
@@ -1,4 +1,4 @@
import { ChevronDown, ChevronUp, MoreHorizontal, Plus, RefreshCcw, Settings2, Trash2 } from 'lucide-react';
import { ChevronDown, ChevronUp, MoreHorizontal, Plus, RefreshCcw, Settings2 } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { DataTableCard, type DataTableColumn } from '../components/data-table';
import { Badge, Button, Card, CardHeader, Modal, Progress } from '../components/ui';
@@ -331,8 +331,6 @@ export function TailscaleStatusPage() {
const [source, setSource] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [deleteTarget, setDeleteTarget] = useState<TailscaleNode | null>(null);
const [deleting, setDeleting] = useState(false);
const online = nodes.filter((node) => node.status === '활성' || node.online).length;
const subnetRouters = nodes.filter((node) => node.routes && node.routes !== '-').length;
@@ -355,20 +353,6 @@ export function TailscaleStatusPage() {
refresh();
}, []);
const deleteNode = () => {
if (!deleteTarget) return;
setDeleting(true);
setError('');
apiRequest<{ deleted: boolean }>(`/tailscale/nodes/${encodeURIComponent(deleteTarget.id)}`, { method: 'DELETE' })
.then(() => {
setNodes((current) => current.filter((node) => node.id !== deleteTarget.id));
setDeleteTarget(null);
refresh();
})
.catch((requestError: Error) => setError(requestError.message))
.finally(() => setDeleting(false));
};
return (
<>
<PageTitle
@@ -401,30 +385,13 @@ export function TailscaleStatusPage() {
</Card>
) : null}
<div className="mt-4">
<TailscaleNodeTable nodes={nodes} loading={loading} onDelete={setDeleteTarget} />
<TailscaleNodeTable nodes={nodes} loading={loading} />
</div>
<Modal
open={Boolean(deleteTarget)}
title="노드 삭제"
onClose={() => !deleting && setDeleteTarget(null)}
footer={
<>
<Button variant="outline" disabled={deleting} onClick={() => setDeleteTarget(null)}>취소</Button>
<Button className="bg-rose-600 text-white hover:bg-rose-500" disabled={deleting} onClick={deleteNode}>
{deleting ? '삭제 중...' : '삭제'}
</Button>
</>
}
>
<p className="text-sm text-muted-foreground">
<strong className="text-foreground">{deleteTarget?.name}</strong> 노드를 Headscale에서 삭제합니다. 연결된 기기의 VPN 연결은 즉시 끊기며, 다시 사용하려면 재등록해야 합니다.
</p>
</Modal>
</>
);
}
function TailscaleNodeTable({ nodes, loading, onDelete }: { nodes: TailscaleNode[]; loading: boolean; onDelete: (node: TailscaleNode) => void }) {
function TailscaleNodeTable({ nodes, loading }: { nodes: TailscaleNode[]; loading: boolean }) {
const pageSize = 10;
const [page, setPage] = useState(1);
const totalPages = Math.max(1, Math.ceil(nodes.length / pageSize));
@@ -453,18 +420,17 @@ function TailscaleNodeTable({ nodes, loading, onDelete }: { nodes: TailscaleNode
<th className="px-5 py-3 font-medium">Routes</th>
<th className="px-5 py-3 font-medium">Last Seen</th>
<th className="px-5 py-3 font-medium">상태</th>
<th className="px-5 py-3 font-medium"><span className="sr-only">관리</span></th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{loading && nodes.length === 0 ? (
<tr>
<td colSpan={8} className="px-5 py-10 text-center text-muted-foreground">노드 정보를 불러오는 중입니다.</td>
<td colSpan={7} className="px-5 py-10 text-center text-muted-foreground">노드 정보를 불러오는 중입니다.</td>
</tr>
) : null}
{!loading && nodes.length === 0 ? (
<tr>
<td colSpan={8} className="px-5 py-10 text-center text-muted-foreground">표시할 노드가 없습니다.</td>
<td colSpan={7} className="px-5 py-10 text-center text-muted-foreground">표시할 노드가 없습니다.</td>
</tr>
) : null}
{pageNodes.map((node) => (
@@ -476,17 +442,6 @@ function TailscaleNodeTable({ nodes, loading, onDelete }: { nodes: TailscaleNode
<td className="px-5 py-3 text-muted-foreground">{node.routes}</td>
<td className="px-5 py-3 text-muted-foreground">{formatTailscaleLastSeen(node.lastSeen)}</td>
<td className="px-5 py-3"><StatusBadge status={node.status} /></td>
<td className="px-5 py-3 text-right">
<Button
variant="ghost"
className="size-8 p-0 text-muted-foreground hover:text-rose-600"
title="노드 삭제"
aria-label={`${node.name} 노드 삭제`}
onClick={() => onDelete(node)}
>
<Trash2 className="size-4" />
</Button>
</td>
</tr>
))}
</tbody>
-3
View File
@@ -11,9 +11,6 @@ export type PlatformDevice = {
os?: string | null;
arch?: string | null;
distro?: string | null;
agentVersion?: string | null;
agentUpdatedAt?: string | null;
agentLastSeenAt?: string | null;
installedAt: string;
lastProvisionedAt: string;
status: string;
@@ -36,13 +36,11 @@ type ImageTags = {
artifacts?: Record<string, string>;
};
function releaseArtifacts(release?: AppRelease | null, appName?: string) {
function releaseArtifacts(release?: AppRelease | null) {
const imageTags = release?.imageTags as ImageTags | null | undefined;
const entries = Object.entries(imageTags?.images ?? {}).filter(([, image]) => (
!/^(postgres|mysql|mariadb|mongo)(:|@)/i.test(image.trim())
));
const entries = Object.entries(imageTags?.images ?? {});
if (entries.length) return entries;
if (release?.artifact) return [[release.app?.name ?? appName ?? 'artifact', release.artifact]];
if (release?.artifact) return [[release.app.name, release.artifact]];
return [];
}
@@ -263,7 +261,7 @@ export function DeploymentManagementPage() {
</div>
) : null}
{pagedDeployments.map((deployment, index) => {
const artifacts = releaseArtifacts(deployment.release, deployment.app?.name);
const artifacts = releaseArtifacts(deployment.release);
return (
<div key={deployment.id} className="rounded-md border border-border bg-background/70 p-4">
<div className="flex items-start justify-between gap-4">
@@ -136,7 +136,6 @@ export function OrganizationsPage() {
},
{ key: 'type', header: '유형', className: 'w-32 px-5 py-3', cell: (row) => <StatusBadge status={organizationTypeLabel(row.type)} tone="blue" /> },
{ key: 'license', header: '라이센스', className: 'w-36 px-5 py-3', cell: (row) => row.defaultLicenseCode ? <StatusBadge status={row.defaultLicenseName ?? row.defaultLicenseCode} tone="green" /> : <span className="text-xs text-muted-foreground">-</span> },
{ key: 'instructorInviteCode', header: '강사 가입 코드', className: 'w-40 px-5 py-3', cell: (row) => <span className="font-mono text-sm font-semibold tracking-[0.08em] text-foreground">{row.instructorInviteCode}</span> },
{ key: 'domain', header: '도메인', className: 'w-56 px-5 py-3', cell: (row) => <span className="font-mono text-xs text-muted-foreground">{row.domain || '-'}</span> },
{ key: 'createdAt', header: '생성', className: 'w-48 px-5 py-3', cell: (row) => <OrganizationCreator organization={row} /> },
{
+1 -33
View File
@@ -13,7 +13,6 @@ import {
lockUser,
terminateUserSession,
unlockUser,
updateLmsInstructor,
type LicensePlan,
type Organization,
type UserSession
@@ -46,7 +45,6 @@ export function UserSessionsPage() {
const [createSaving, setCreateSaving] = useState(false);
const [menuSession, setMenuSession] = useState<{ id: string; x: number; y: number } | null>(null);
const [detailSession, setDetailSession] = useState<UserSession | null>(null);
const [lmsRoleSaving, setLmsRoleSaving] = useState(false);
const loadingVisible = useMinimumLoading(loading);
const refresh = () => {
@@ -185,24 +183,6 @@ export function UserSessionsPage() {
.finally(() => setCreateSaving(false));
};
const setLmsInstructor = (isInstructor: boolean) => {
if (!detailSession) return;
setLmsRoleSaving(true);
updateLmsInstructor(detailSession.userId, isInstructor)
.then(() => {
setDetailSession((current) => current ? { ...current, isLmsInstructor: isInstructor } : current);
setSessions((current) => current.map((session) => (
session.userId === detailSession.userId ? { ...session, isLmsInstructor: isInstructor } : session
)));
toast({ type: 'success', title: 'LMS 역할 변경', message: isInstructor ? '강사 권한을 부여했습니다.' : '강사 권한을 해제했습니다.' });
})
.catch((error: Error) => {
toast({ type: 'error', title: 'LMS 역할 변경 실패', message: error.message });
})
.finally(() => setLmsRoleSaving(false));
};
return (
<>
<PageTitle
@@ -309,20 +289,8 @@ export function UserSessionsPage() {
</div>
<StatusBadge status={detailSession.state} />
</div>
<div className="grid gap-3 text-sm sm:grid-cols-2">
<div className="grid gap-3 text-sm">
<DetailItem label="사용자 ID" value={detailSession.userId} mono />
<label className="block min-w-0">
<span className="text-xs font-medium text-muted-foreground">LMS</span>
<select
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm font-medium text-foreground outline-none focus:ring-2 focus:ring-ring"
value={detailSession.isLmsInstructor ? 'INSTRUCTOR' : 'STUDENT'}
disabled={lmsRoleSaving}
onChange={(event) => setLmsInstructor(event.target.value === 'INSTRUCTOR')}
>
<option value="STUDENT">수강생</option>
<option value="INSTRUCTOR">강사</option>
</select>
</label>
<DetailItem label="조직" value={detailSession.primaryAffiliation?.organizationName ?? '-'} />
<DetailItem label="조직 유형" value={organizationTypeLabel(detailSession.primaryAffiliation?.organizationType)} />
<DetailItem label="부서" value={detailSession.primaryAffiliation?.department ?? '-'} />
-9
View File
@@ -70,7 +70,6 @@ export type UserSession = {
expiresAt?: string | null;
lastSeen?: string | null;
state: string;
isLmsInstructor?: boolean;
primaryAffiliation?: {
id: string;
organizationId: string;
@@ -91,7 +90,6 @@ export type Organization = {
domain?: string | null;
defaultLicenseCode?: string | null;
defaultLicenseName?: string | null;
instructorInviteCode: string;
createdById?: string | null;
createdByName?: string | null;
createdByEmail?: string | null;
@@ -259,13 +257,6 @@ export function unlockUser(userId: string) {
});
}
export function updateLmsInstructor(userId: string, isInstructor: boolean) {
return apiRequest<{ userId: string; isInstructor: boolean }>(`/users/${userId}/lms-instructor`, {
method: 'PATCH',
body: JSON.stringify({ isInstructor })
});
}
export function terminateUserSession(sessionId: string) {
return apiRequest(`/users/sessions/${sessionId}`, {
method: 'DELETE'
@@ -22,8 +22,6 @@ type HeadscaleNode = {
type AclRule = { id: string; action: string; sources: string[]; destinations: string[] };
type PlatformRow = HeadscaleNode & { device?: PlatformDevice; controllers: string[]; no: number };
type DeviceKind = 'agent' | 'launcher' | 'unknown';
function normalize(value?: string | null) {
return (value ?? '').toLowerCase().replace(/[^a-z0-9]/g, '');
}
@@ -43,17 +41,6 @@ function findControllers(node: HeadscaleNode, rules: AclRule[]) {
.flatMap((rule) => rule.sources))];
}
function getDeviceKind(row: PlatformRow): DeviceKind {
if (!row.device) return 'unknown';
return row.device.agentVersion || row.device.agentLastSeenAt ? 'agent' : 'launcher';
}
function formatDateTime(value?: string | null) {
if (!value) return '-';
const date = new Date(value);
return Number.isNaN(date.getTime()) ? '-' : date.toLocaleString('ko-KR', { hour12: false });
}
export function RuntimeDeviceControlPage() {
const [rows, setRows] = useState<PlatformRow[]>([]);
const [loading, setLoading] = useState(true);
@@ -104,19 +91,6 @@ export function RuntimeDeviceControlPage() {
width: 180,
cell: (row) => <span className="font-mono text-xs text-muted-foreground">{row.ip || '-'}</span>,
},
{
key: 'deviceKind',
header: '유형',
width: 150,
cell: (row) => {
const kind = getDeviceKind(row);
if (kind === 'agent') {
return <Badge tone="green">에이전트</Badge>;
}
if (kind === 'launcher') return <Badge tone="neutral">런처 클라이언트</Badge>;
return <Badge tone="neutral">미확인</Badge>;
},
},
{
key: 'controllers',
header: '제어 사용자',
@@ -181,9 +155,6 @@ export function RuntimeDeviceControlPage() {
<dt className="text-muted-foreground">Workspace</dt><dd className="min-w-0 break-all">{selected?.device?.workspaceRoot || '-'}</dd>
<dt className="text-muted-foreground">SSH 사용자</dt><dd>{selected?.device?.sshUser || '-'}</dd>
<dt className="text-muted-foreground">사용 유형</dt><dd>{selected?.device?.usageType || '-'}</dd>
<dt className="text-muted-foreground">장비 유형</dt><dd>{selected ? ({ agent: '에이전트', launcher: '런처 클라이언트', unknown: '미확인' }[getDeviceKind(selected)]) : '-'}</dd>
<dt className="text-muted-foreground">에이전트 버전</dt><dd>{selected?.device?.agentVersion || '-'}</dd>
<dt className="text-muted-foreground">마지막 에이전트 확인</dt><dd>{formatDateTime(selected?.device?.agentLastSeenAt)}</dd>
</dl>
</Modal>
</>
-1
View File
@@ -21,7 +21,6 @@ export type BuildJob = {
branch: string;
commitSha?: string | null;
artifact?: string | null;
artifactSha256?: string | null;
image?: string | null;
status: JobStatus;
logs?: string | null;