add infrastructure resource YAML viewer and fix PVC visibility
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
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 { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||||
import { DataTableCard, type DataTableColumn, type DataTableFilter } from '../../components/data-table';
|
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 { PageTitle } from '../../layout/AppLayout';
|
||||||
import { runtimeStatusDotClass } from '../../lib/status-dot';
|
import { runtimeStatusDotClass } from '../../lib/status-dot';
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
import type { RuntimePodMetric, RuntimeResource, RuntimeResourceResponse } from '../../types/runtime';
|
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';
|
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
|
||||||
|
|
||||||
const defaultNamespaces = ['studio-system', 'stdo-lms', 'stdo-ads-ide'];
|
const defaultNamespaces = ['studio-system', 'stdo-lms', 'stdo-ads-ide'];
|
||||||
@@ -40,7 +40,7 @@ function isNetwork(resource: RuntimeResource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isStorage(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' {
|
function resourceStatusTone(status: string): 'neutral' | 'green' | 'amber' | 'red' | 'blue' {
|
||||||
@@ -191,6 +191,22 @@ function ResourceTable({
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
emptyMessage: string;
|
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>>>(
|
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> },
|
{ 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: '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: '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: '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]);
|
const filters = useMemo(() => [NamespaceFilter(rows), KindFilter(rows)], [rows]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<DataTableCard
|
<DataTableCard
|
||||||
title={title}
|
title={title}
|
||||||
description={description}
|
description={description}
|
||||||
@@ -219,10 +252,23 @@ function ResourceTable({
|
|||||||
getSearchText={(row) => `${row.namespace} ${row.kind} ${row.name} ${row.status} ${row.detail}`}
|
getSearchText={(row) => `${row.namespace} ${row.kind} ${row.name} ${row.status} ${row.detail}`}
|
||||||
getRowKey={(row) => row.id}
|
getRowKey={(row) => row.id}
|
||||||
pageSize={10}
|
pageSize={10}
|
||||||
minWidth={1280}
|
minWidth={1320}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
emptyMessage={emptyMessage}
|
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`);
|
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) {
|
export function loadRuntimePodMetrics(namespace?: string) {
|
||||||
const query = namespace ? `?namespace=${encodeURIComponent(namespace)}` : '';
|
const query = namespace ? `?namespace=${encodeURIComponent(namespace)}` : '';
|
||||||
return apiRequest<RuntimePodMetric[]>(`/runtime/metrics/pods${query}`);
|
return apiRequest<RuntimePodMetric[]>(`/runtime/metrics/pods${query}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user