merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-09 10:22:15 +09:00
4 changed files with 39 additions and 19 deletions
+2
View File
@@ -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;
+8 -8
View File
@@ -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> </div>
<p className="mt-1 truncate text-sm text-muted-foreground">{pipeline.message || '-'}</p> <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"> </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)}> <Badge tone={pipelineStatusTone(pipeline.status)}>
<span className="inline-flex items-center gap-1.5"> <span className="inline-flex items-center gap-1.5">
{isActivePipelineStatus(pipeline.status) ? <Loader2 className="size-3 animate-spin" /> : null} {isActivePipelineStatus(pipeline.status) ? <Loader2 className="size-3 animate-spin" /> : null}
{pipeline.status || '-'} {pipeline.status || '-'}
</span> </span>
</Badge> </Badge>
<span>{pipeline.branch || '-'}</span> <span className="rounded-md bg-muted/50 px-2 py-1">{pipeline.branch || '-'}</span>
<span>{formatPipelineRelativeTime(pipeline.createdAt)}</span> <span className="rounded-md bg-muted/50 px-2 py-1 font-mono">SHA1 {formatShortSha(pipeline.commitSha)}</span>
</div> <span className="rounded-md bg-muted/50 px-2 py-1">소요 {formatDuration(pipeline.durationSeconds)}</span>
</div> <span className="min-w-16 text-right">{formatPipelineRelativeTime(pipeline.createdAt)}</span>
</div> </div>
</div> </div>
))} ))}
+2
View File
@@ -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;
+16
View File
@@ -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';