This commit is contained in:
@@ -248,6 +248,8 @@ type CommonAppPipeline = {
|
|||||||
repository: string;
|
repository: string;
|
||||||
message: string;
|
message: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
|
commitSha?: string | null;
|
||||||
|
durationSeconds?: number | null;
|
||||||
status: string;
|
status: string;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { PageTitle } from '../../layout/AppLayout';
|
|||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
|
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
|
||||||
import { listCommonAppPipelines, type CommonAppPipeline } from './common-app-api';
|
import { listCommonAppPipelines, type CommonAppPipeline } from './common-app-api';
|
||||||
import { formatPipelineRelativeTime, formatRepositoryName, isActivePipelineStatus, pipelineStatusDotClass, pipelineStatusTone } from './common-app-utils';
|
import { formatDuration, formatPipelineRelativeTime, formatRepositoryName, formatShortSha, isActivePipelineStatus, pipelineStatusDotClass, pipelineStatusTone } from './common-app-utils';
|
||||||
|
|
||||||
export function CommonAppBuildPage() {
|
export function CommonAppBuildPage() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@@ -77,25 +77,25 @@ export function CommonAppBuildPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
<div className="divide-y divide-border">
|
<div className="divide-y divide-border">
|
||||||
{pipelines.map((pipeline) => (
|
{pipelines.map((pipeline) => (
|
||||||
<div key={pipeline.id} className="px-5 py-4 transition-colors hover:bg-muted/20">
|
<div key={pipeline.id} className="flex flex-col gap-3 px-5 py-3 transition-colors hover:bg-muted/20 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="min-w-0">
|
||||||
<div className="min-w-0">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<span className={cn('size-2.5 shrink-0 rounded-full', pipelineStatusDotClass(pipeline.status))} />
|
||||||
<span className={cn('size-2.5 shrink-0 rounded-full', pipelineStatusDotClass(pipeline.status))} />
|
<p className="truncate text-sm font-semibold">{formatRepositoryName(pipeline.repository)}</p>
|
||||||
<p className="truncate text-sm font-semibold">{formatRepositoryName(pipeline.repository)}</p>
|
|
||||||
</div>
|
|
||||||
<p className="mt-1 truncate text-sm text-muted-foreground">{pipeline.message || '-'}</p>
|
|
||||||
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
|
||||||
<Badge tone={pipelineStatusTone(pipeline.status)}>
|
|
||||||
<span className="inline-flex items-center gap-1.5">
|
|
||||||
{isActivePipelineStatus(pipeline.status) ? <Loader2 className="size-3 animate-spin" /> : null}
|
|
||||||
{pipeline.status || '-'}
|
|
||||||
</span>
|
|
||||||
</Badge>
|
|
||||||
<span>{pipeline.branch || '-'}</span>
|
|
||||||
<span>{formatPipelineRelativeTime(pipeline.createdAt)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="mt-1 truncate text-sm text-muted-foreground">{pipeline.message || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 flex-wrap items-center gap-2 text-xs text-muted-foreground md:justify-end">
|
||||||
|
<Badge tone={pipelineStatusTone(pipeline.status)}>
|
||||||
|
<span className="inline-flex items-center gap-1.5">
|
||||||
|
{isActivePipelineStatus(pipeline.status) ? <Loader2 className="size-3 animate-spin" /> : null}
|
||||||
|
{pipeline.status || '-'}
|
||||||
|
</span>
|
||||||
|
</Badge>
|
||||||
|
<span className="rounded-md bg-muted/50 px-2 py-1">{pipeline.branch || '-'}</span>
|
||||||
|
<span className="rounded-md bg-muted/50 px-2 py-1 font-mono">SHA1 {formatShortSha(pipeline.commitSha)}</span>
|
||||||
|
<span className="rounded-md bg-muted/50 px-2 py-1">소요 {formatDuration(pipeline.durationSeconds)}</span>
|
||||||
|
<span className="min-w-16 text-right">{formatPipelineRelativeTime(pipeline.createdAt)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export type CommonAppPipeline = {
|
|||||||
repository: string;
|
repository: string;
|
||||||
message: string;
|
message: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
|
commitSha?: string | null;
|
||||||
|
durationSeconds?: number | null;
|
||||||
status: string;
|
status: string;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
ciUrl?: string | null;
|
ciUrl?: string | null;
|
||||||
|
|||||||
@@ -75,6 +75,22 @@ export function formatRepositoryName(repository: string) {
|
|||||||
return repository.replace('/', ' / ');
|
return repository.replace('/', ' / ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatShortSha(value?: string | null) {
|
||||||
|
if (!value) return '-';
|
||||||
|
return value.length > 8 ? value.slice(0, 8) : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDuration(seconds?: number | null) {
|
||||||
|
if (seconds === null || seconds === undefined || !Number.isFinite(seconds)) return '-';
|
||||||
|
if (seconds < 60) return `${seconds}초`;
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
if (minutes < 60) return remainingSeconds ? `${minutes}분 ${remainingSeconds}초` : `${minutes}분`;
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const remainingMinutes = minutes % 60;
|
||||||
|
return remainingMinutes ? `${hours}시간 ${remainingMinutes}분` : `${hours}시간`;
|
||||||
|
}
|
||||||
|
|
||||||
export function pipelineStatusTone(status?: string | null): 'green' | 'amber' | 'red' | 'blue' | 'neutral' {
|
export function pipelineStatusTone(status?: string | null): 'green' | 'amber' | 'red' | 'blue' | 'neutral' {
|
||||||
const value = (status ?? '').toLowerCase();
|
const value = (status ?? '').toLowerCase();
|
||||||
if (['success', 'succeeded', 'passed'].includes(value)) return 'green';
|
if (['success', 'succeeded', 'passed'].includes(value)) return 'green';
|
||||||
|
|||||||
Reference in New Issue
Block a user