add runtime log views and standardize status dots
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-09 10:16:25 +09:00
parent f67017623f
commit 3cab64a256
13 changed files with 990 additions and 65 deletions
+3 -9
View File
@@ -5,6 +5,7 @@ import { useToast } from '../../components/toast';
import { PageTitle } from '../../layout/AppLayout';
import { formatDateTime } from '../../lib/date-format';
import { getStoredSelectedAppId, getStoredSelectedComponentId, selectedComponentChangedEventName, setStoredSelectedAppId, setStoredSelectedComponentId } from '../../lib/selected-app';
import { statusDotToneClass } from '../../lib/status-dot';
import type { ApiApp, ApiAppComponent, RuntimeEnvProfile, RuntimeEnvVariable, RuntimeIngressRule, RuntimeResource } from '../../types/runtime';
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
import {
@@ -40,13 +41,6 @@ function getWorkloadKind(kind: string) {
return '';
}
function statusDot(status?: string) {
if (['Running', 'Bound', 'ClusterIP', 'LoadBalancer'].includes(status ?? '')) return 'bg-emerald-400';
if (['Pending', 'ContainerCreating'].includes(status ?? '')) return 'bg-amber-400';
if (['Error', 'CrashLoopBackOff', 'ImagePullBackOff', 'Failed'].includes(status ?? '')) return 'bg-rose-400';
return 'bg-muted-foreground';
}
function isStopped(resource: RuntimeResource) {
return resource.ready.endsWith('/0');
}
@@ -520,7 +514,7 @@ export function RuntimeConfigPage() {
<tr key={variable.id} className="hover:bg-muted/20">
<td className="px-4 py-3">
<span className="inline-flex items-center gap-2">
<span className={variable.isSecret ? 'block size-2.5 rounded-full bg-amber-400' : 'block size-2.5 rounded-full bg-emerald-400'} />
<span className={`block size-2.5 shrink-0 rounded-full ${variable.isSecret ? statusDotToneClass('amber') : statusDotToneClass('green')}`} />
<span className="truncate">{variable.component?.name ?? '-'}</span>
</span>
</td>
@@ -626,7 +620,7 @@ export function RuntimeConfigPage() {
<tr key={rule.id} className="hover:bg-muted/20">
<td className="px-4 py-3">
<span className="inline-flex items-center gap-2">
<span className="block size-2.5 rounded-full bg-sky-400" />
<span className={`block size-2.5 shrink-0 rounded-full ${statusDotToneClass('blue')}`} />
<span className="truncate">{rule.component?.name ?? '-'}</span>
</span>
</td>
+8 -14
View File
@@ -4,18 +4,12 @@ import { Badge, Button, Card } from '../../components/ui';
import { PageTitle } from '../../layout/AppLayout';
import { formatDateTime } from '../../lib/date-format';
import { getStoredSelectedAppId, setStoredSelectedAppId } from '../../lib/selected-app';
import { runtimeStatusDotClass, statusDotToneClass } from '../../lib/status-dot';
import { cn } from '../../lib/utils';
import type { ApiApp, RuntimeEvent, RuntimePodMetric, RuntimeResource, RuntimeResourceResponse } from '../../types/runtime';
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
import { loadApps, loadRuntimeEvents, loadRuntimePodMetrics, loadRuntimeResources, loadRuntimeWorkloadLogs } from './runtime-api';
function statusDot(status?: string) {
if (['Running', 'Bound', 'ClusterIP', 'LoadBalancer', 'Normal'].includes(status ?? '')) return 'bg-emerald-400';
if (['Pending', 'ContainerCreating', 'Warning'].includes(status ?? '')) return 'bg-amber-400';
if (['Error', 'CrashLoopBackOff', 'ImagePullBackOff', 'Failed'].includes(status ?? '')) return 'bg-rose-400';
return 'bg-muted-foreground';
}
function eventTone(type?: string) {
if (type === 'Normal') return 'green';
if (type === 'Warning') return 'amber';
@@ -59,12 +53,12 @@ function isActiveLogTarget(resource: RuntimeResource) {
}
function workloadStatusDot(resource: RuntimeResource) {
if (resource.kind === 'Pod') return statusDot(resource.status);
if (resource.kind === 'Job') return resource.status === 'Running' ? 'bg-emerald-400' : 'bg-muted-foreground';
if (hasReadyReplica(resource) && resource.status === 'Running') return 'bg-emerald-400';
if (resource.status === 'Pending') return 'bg-amber-400';
if (['Error', 'CrashLoopBackOff', 'ImagePullBackOff', 'Failed'].includes(resource.status)) return 'bg-rose-400';
return 'bg-muted-foreground';
if (resource.kind === 'Pod') return runtimeStatusDotClass(resource.status);
if (resource.kind === 'Job') return resource.status === 'Running' ? statusDotToneClass('green') : statusDotToneClass('neutral');
if (hasReadyReplica(resource) && resource.status === 'Running') return statusDotToneClass('green');
if (resource.status === 'Pending') return statusDotToneClass('amber');
if (['Error', 'CrashLoopBackOff', 'ImagePullBackOff', 'Failed'].includes(resource.status)) return statusDotToneClass('red');
return statusDotToneClass('neutral');
}
function metricKey(namespace: string, pod: string) {
@@ -311,7 +305,7 @@ export function RuntimeLogsPage() {
onClick={() => loadLogs(resource)}
>
<div className="flex items-center gap-2">
<span className={cn('size-2 rounded-full', workloadStatusDot(resource))} />
<span className={cn('size-2.5 shrink-0 rounded-full', workloadStatusDot(resource))} />
<span className="truncate text-sm font-medium">{resource.name}</span>
</div>
<div className="mt-2 flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">