This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { RotateCcw } from 'lucide-react';
|
||||
import { Braces, RotateCcw } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||
import { DataTableCard, type DataTableColumn, type DataTableFilter } from '../../components/data-table';
|
||||
import { Badge, Button, Card, CardHeader } from '../../components/ui';
|
||||
import { Badge, Button, Card, CardHeader, Modal } from '../../components/ui';
|
||||
import { PageTitle } from '../../layout/AppLayout';
|
||||
import { runtimeStatusDotClass } from '../../lib/status-dot';
|
||||
import { cn } from '../../lib/utils';
|
||||
import type { RuntimePodMetric, RuntimeResource, RuntimeResourceResponse } from '../../types/runtime';
|
||||
import { loadApps, loadRuntimeEvents, loadRuntimePodMetrics, loadRuntimeResources } from '../runtime/runtime-api';
|
||||
import { loadApps, loadRuntimePodMetrics, loadRuntimeResourceYaml, loadRuntimeResources } from '../runtime/runtime-api';
|
||||
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
|
||||
|
||||
const defaultNamespaces = ['studio-system', 'stdo-lms', 'stdo-ads-ide'];
|
||||
@@ -40,7 +40,7 @@ function isNetwork(resource: RuntimeResource) {
|
||||
}
|
||||
|
||||
function isStorage(resource: RuntimeResource) {
|
||||
return ['PersistentVolumeClaim', 'PersistentVolume', 'StorageClass'].includes(resource.kind);
|
||||
return ['PersistentVolumeClaim', 'PVC', 'PersistentVolume', 'StorageClass'].includes(resource.kind);
|
||||
}
|
||||
|
||||
function resourceStatusTone(status: string): 'neutral' | 'green' | 'amber' | 'red' | 'blue' {
|
||||
@@ -191,6 +191,22 @@ function ResourceTable({
|
||||
loading: boolean;
|
||||
emptyMessage: string;
|
||||
}) {
|
||||
const [yamlTarget, setYamlTarget] = useState<ResourceRow | null>(null);
|
||||
const [yaml, setYaml] = useState('');
|
||||
const [yamlLoading, setYamlLoading] = useState(false);
|
||||
const [yamlError, setYamlError] = useState('');
|
||||
|
||||
const openYaml = (row: ResourceRow) => {
|
||||
setYamlTarget(row);
|
||||
setYaml('');
|
||||
setYamlError('');
|
||||
setYamlLoading(true);
|
||||
loadRuntimeResourceYaml(row.namespace, row.kind, row.name)
|
||||
.then((response) => setYaml(response.yaml))
|
||||
.catch((error: unknown) => setYamlError(error instanceof Error ? error.message : 'YAML을 불러오지 못했습니다.'))
|
||||
.finally(() => setYamlLoading(false));
|
||||
};
|
||||
|
||||
const columns = useMemo<Array<DataTableColumn<ResourceRow>>>(
|
||||
() => [
|
||||
{ key: 'namespace', header: '네임스페이스', className: 'w-44 px-5 py-3', cell: (row) => <span className="font-medium">{row.namespace}</span> },
|
||||
@@ -201,7 +217,23 @@ function ResourceTable({
|
||||
{ key: 'restarts', header: '재시작', className: 'w-24 px-5 py-3', cell: (row) => <span className="text-muted-foreground">{row.restarts ?? '-'}</span> },
|
||||
{ key: 'metric', header: '사용량', className: 'w-40 px-5 py-3', cell: (row) => <span className="text-xs text-muted-foreground">{row.metric ? `${row.metric.cpu} / ${row.metric.memory}` : '-'}</span> },
|
||||
{ key: 'age', header: 'Age', className: 'w-28 px-5 py-3', cell: (row) => <span className="text-muted-foreground">{row.age || '-'}</span> },
|
||||
{ key: 'detail', header: '상세', className: 'min-w-[260px] px-5 py-3', cell: (row) => <span className="text-xs text-muted-foreground">{row.detail || '-'}</span> }
|
||||
{ key: 'detail', header: '상세', className: 'min-w-[220px] px-5 py-3', cell: (row) => <span className="text-xs text-muted-foreground">{row.detail || '-'}</span> },
|
||||
{
|
||||
key: 'yaml',
|
||||
header: '',
|
||||
className: 'w-14 px-3 py-3 text-right',
|
||||
cell: (row) => (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-8 px-0"
|
||||
title="YAML 보기"
|
||||
aria-label="YAML 보기"
|
||||
onClick={() => openYaml(row)}
|
||||
>
|
||||
<Braces className="size-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
],
|
||||
[]
|
||||
);
|
||||
@@ -209,6 +241,7 @@ function ResourceTable({
|
||||
const filters = useMemo(() => [NamespaceFilter(rows), KindFilter(rows)], [rows]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTableCard
|
||||
title={title}
|
||||
description={description}
|
||||
@@ -219,10 +252,23 @@ function ResourceTable({
|
||||
getSearchText={(row) => `${row.namespace} ${row.kind} ${row.name} ${row.status} ${row.detail}`}
|
||||
getRowKey={(row) => row.id}
|
||||
pageSize={10}
|
||||
minWidth={1280}
|
||||
minWidth={1320}
|
||||
loading={loading}
|
||||
emptyMessage={emptyMessage}
|
||||
/>
|
||||
<Modal
|
||||
open={Boolean(yamlTarget)}
|
||||
title={yamlTarget ? `${yamlTarget.kind}/${yamlTarget.name}` : 'YAML'}
|
||||
onClose={() => setYamlTarget(null)}
|
||||
className="!max-w-5xl"
|
||||
>
|
||||
{yamlLoading ? <p className="py-10 text-center text-sm text-muted-foreground">불러오는 중...</p> : null}
|
||||
{yamlError ? <p className="py-4 text-sm text-rose-300">{yamlError}</p> : null}
|
||||
{!yamlLoading && !yamlError ? (
|
||||
<pre className="max-h-[65vh] overflow-auto rounded-md border border-border bg-black/30 p-4 text-xs leading-6 text-slate-200">{yaml}</pre>
|
||||
) : null}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,12 @@ export function loadRuntimeResources(namespace: string) {
|
||||
return apiRequest<RuntimeResourceResponse>(`/apps/runtime-namespaces/${encodeURIComponent(namespace)}/resources`);
|
||||
}
|
||||
|
||||
export function loadRuntimeResourceYaml(namespace: string, kind: string, name: string) {
|
||||
return apiRequest<{ namespace: string; kind: string; name: string; yaml: string }>(
|
||||
`/apps/runtime-namespaces/${encodeURIComponent(namespace)}/resources/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/yaml`
|
||||
);
|
||||
}
|
||||
|
||||
export function loadRuntimePodMetrics(namespace?: string) {
|
||||
const query = namespace ? `?namespace=${encodeURIComponent(namespace)}` : '';
|
||||
return apiRequest<RuntimePodMetric[]>(`/runtime/metrics/pods${query}`);
|
||||
|
||||
Reference in New Issue
Block a user