Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions web/components/builds/build-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
ItemDescription,
ItemTitle,
} from "@/components/ui/item";
import { StatusBadge } from "@/components/ui/status-badge";
import type { Build, BuildStatus, GithubRepo, Service } from "@/db/types";
import { formatElapsedDurationBetween, formatRelativeTime } from "@/lib/date";
import { fetcher } from "@/lib/fetcher";
Expand All @@ -46,56 +47,47 @@ const STATUS_CONFIG: Record<
{
icon: typeof CheckCircle2;
color: string;
bgColor: string;
label: string;
}
> = {
pending: {
icon: Clock,
color: "text-slate-500",
bgColor: "bg-slate-500/10",
label: "Queued",
},
claimed: {
icon: CircleDashed,
color: "text-blue-500",
bgColor: "bg-blue-500/10",
label: "Starting",
},
cloning: {
icon: Loader2,
color: "text-blue-500",
bgColor: "bg-blue-500/10",
label: "Cloning",
},
building: {
icon: Loader2,
color: "text-blue-500",
bgColor: "bg-blue-500/10",
label: "Building",
},
pushing: {
icon: Loader2,
color: "text-blue-500",
bgColor: "bg-blue-500/10",
label: "Pushing",
},
completed: {
icon: CheckCircle2,
color: "text-green-500",
bgColor: "bg-green-500/10",
label: "Completed",
},
failed: {
icon: XCircle,
color: "text-red-500",
bgColor: "bg-red-500/10",
label: "Failed",
},
cancelled: {
icon: AlertCircle,
color: "text-slate-500",
bgColor: "bg-slate-500/10",
label: "Cancelled",
},
};
Expand Down Expand Up @@ -128,7 +120,6 @@ export function BuildDetails({

const build = data?.build || initialBuild;
const config = STATUS_CONFIG[build.status];
const Icon = config.icon;
const isAnimated = ["cloning", "building", "pushing"].includes(build.status);

const handleCancel = async () => {
Expand Down Expand Up @@ -188,12 +179,12 @@ export function BuildDetails({
</Button>
<div className="flex-1">
<div className="flex items-center gap-3">
<span
className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium ${config.color} ${config.bgColor}`}
>
<Icon className={`size-4 ${isAnimated ? "animate-spin" : ""}`} />
{config.label}
</span>
<StatusBadge
icon={config.icon}
label={config.label}
isAnimated={isAnimated}
className={config.color}
/>
<code className="font-mono text-sm">
{build.commitSha.slice(0, 7)}
</code>
Expand Down
31 changes: 10 additions & 21 deletions web/components/builds/builds-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,14 @@ import {
ItemTitle,
} from "@/components/ui/item";
import { Spinner } from "@/components/ui/spinner";
import { StatusBadge } from "@/components/ui/status-badge";
import type { Build, BuildStatus } from "@/db/types";
import { formatElapsedDurationBetween, formatRelativeTime } from "@/lib/date";
import { fetcher } from "@/lib/fetcher";

type BuildListItem = Pick<
Build,
| "id"
| "commitSha"
| "commitMessage"
| "branch"
| "author"
| "status"
| "error"
"id" | "commitSha" | "commitMessage" | "branch" | "author" | "status"
> & {
createdAt: string;
startedAt: string | null;
Expand Down Expand Up @@ -103,18 +98,17 @@ const STATUS_CONFIG: Record<
},
};

function StatusBadge({ status }: { status: BuildStatus }) {
function BuildStatusBadge({ status }: { status: BuildStatus }) {
const config = STATUS_CONFIG[status];
const Icon = config.icon;
const isAnimated = ["cloning", "building", "pushing"].includes(status);

return (
<span
className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium ${config.color} bg-current/10`}
>
<Icon className={`size-3.5 ${isAnimated ? "animate-spin" : ""}`} />
{config.label}
</span>
<StatusBadge
icon={config.icon}
label={config.label}
isAnimated={isAnimated}
className={config.color}
/>
);
}

Expand Down Expand Up @@ -243,7 +237,7 @@ export function BuildsViewer({
)
}
>
<StatusBadge status={build.status} />
<BuildStatusBadge status={build.status} />
<ItemContent>
<ItemTitle>
<GitCommit className="size-3.5 text-muted-foreground" />
Expand Down Expand Up @@ -275,11 +269,6 @@ export function BuildsViewer({
</span>
)}
</ItemDescription>
{build.error && (
<pre className="mt-1 max-w-full overflow-x-auto break-words rounded bg-red-500/10 p-2 font-mono text-xs text-red-500 whitespace-pre-wrap">
{build.error}
</pre>
)}
</ItemContent>
<ItemActions onClick={(e) => e.stopPropagation()}>
{canCancel(build.status) && (
Expand Down
69 changes: 37 additions & 32 deletions web/components/service/details/changelog-history.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import {
ArrowRight,
CheckCircle2,
Clock,
GitCommitHorizontal,
Expand All @@ -12,7 +11,6 @@ import {
import Link from "next/link";
import { useMemo } from "react";
import useSWRInfinite from "swr/infinite";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Empty,
Expand All @@ -21,6 +19,7 @@ import {
EmptyTitle,
} from "@/components/ui/empty";
import { Skeleton } from "@/components/ui/skeleton";
import { StatusBadge } from "@/components/ui/status-badge";
import type { RolloutStatus } from "@/db/types";
import { formatDateTime, formatRelativeTime } from "@/lib/date";
import { fetcher } from "@/lib/fetcher";
Expand Down Expand Up @@ -68,23 +67,19 @@ function RolloutBadge({
icon: Clock,
className: "text-muted-foreground",
};
const Icon = config.icon;

return (
<Badge
variant="outline"
<StatusBadge
icon={config.icon}
label={config.label}
isAnimated={rollout.status === "in_progress"}
className={config.className}
render={
<Link
href={`/dashboard/projects/${projectSlug}/${envName}/services/${serviceId}/rollouts/${rollout.id}`}
/>
}
>
<Icon
className={rollout.status === "in_progress" ? "animate-spin" : ""}
/>
{config.label}
</Badge>
/>
);
}

Expand Down Expand Up @@ -127,18 +122,25 @@ function RevisionChanges({ item }: { item: ServiceRevisionChangelogItem }) {
}

return (
<div className="divide-y">
<div className="space-y-1.5 font-mono text-sm">
{item.comparison.changes.map((change) => (
<div
key={`${change.field}:${change.from}:${change.to}`}
className="grid gap-2 py-2.5 text-sm sm:grid-cols-[minmax(8rem,0.65fr)_minmax(0,1.35fr)] sm:gap-4"
className="flex items-baseline justify-between gap-4"
>
<div className="font-medium text-foreground">{change.field}</div>
<div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-2 text-muted-foreground">
<span className="break-words">{change.from}</span>
<ArrowRight className="size-3.5 shrink-0" />
<span className="break-words text-foreground">{change.to}</span>
</div>
<span className="shrink-0 text-muted-foreground">{change.field}</span>
<span className="flex min-w-0 items-baseline justify-end gap-1.5">
<span
className="truncate text-muted-foreground"
title={change.from}
>
{change.from}
</span>
<span className="shrink-0 text-muted-foreground">→</span>
<span className="truncate font-medium" title={change.to}>
{change.to}
</span>
</span>
</div>
))}
</div>
Expand Down Expand Up @@ -219,29 +221,30 @@ export function ChangelogHistory({
</EmptyDescription>
</Empty>
) : (
<div className="grid gap-2">
{revisions.map((revision) => (
<article
key={revision.id}
className="space-y-3 rounded-lg border p-4"
>
<div className="flex flex-wrap items-start justify-between gap-2">
<div>
<div className="font-medium">
<div>
{revisions.map((revision, index) => (
<article key={revision.id} className="relative pb-6 pl-6 last:pb-0">
{index < revisions.length - 1 ? (
<span className="absolute top-5 -bottom-1 left-[3.5px] w-px bg-border" />
) : null}
<div className="relative flex flex-wrap items-center justify-between gap-x-4 gap-y-1">
<span className="-left-6 absolute top-1/2 size-2 -translate-y-1/2 rounded-full bg-muted-foreground/40" />
<div className="flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-0.5">
<span className="text-sm">
{revision.comparison.kind === "initial"
? "Initial revision"
: revision.comparison.kind === "changes" &&
revision.comparison.changes.length === 0
? "Redeployed"
: "Configuration updated"}
</div>
<div
</span>
<span
className="text-xs text-muted-foreground"
title={formatDateTime(revision.createdAt)}
>
{formatRelativeTime(revision.createdAt)} ·{" "}
{revision.id.slice(0, 8)}
</div>
</span>
</div>
{revision.rollout ? (
<RolloutBadge
Expand All @@ -252,7 +255,9 @@ export function ChangelogHistory({
/>
) : null}
</div>
<RevisionChanges item={revision} />
<div className="mt-2 rounded-md border px-3 py-2.5">
<RevisionChanges item={revision} />
</div>
</article>
))}
</div>
Expand Down
20 changes: 7 additions & 13 deletions web/components/service/details/rollout-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { useRouter } from "next/navigation";
import { LogViewer } from "@/components/logs/log-viewer";
import { Button } from "@/components/ui/button";
import { StatusBadge } from "@/components/ui/status-badge";
import type { Rollout, RolloutStatus, Service } from "@/db/types";
import { formatElapsedDurationBetween, formatRelativeTime } from "@/lib/date";

Expand All @@ -24,38 +25,32 @@ const STATUS_CONFIG: Record<
{
icon: typeof CheckCircle2;
color: string;
bgColor: string;
label: string;
}
> = {
queued: {
icon: Clock,
color: "text-slate-500",
bgColor: "bg-slate-500/10",
label: "Queued",
},
in_progress: {
icon: Loader2,
color: "text-blue-500",
bgColor: "bg-blue-500/10",
label: "In Progress",
},
completed: {
icon: CheckCircle2,
color: "text-green-500",
bgColor: "bg-green-500/10",
label: "Completed",
},
failed: {
icon: XCircle,
color: "text-red-500",
bgColor: "bg-red-500/10",
label: "Failed",
},
rolled_back: {
icon: RotateCcw,
color: "text-orange-500",
bgColor: "bg-orange-500/10",
label: "Rolled Back",
},
};
Expand Down Expand Up @@ -88,7 +83,6 @@ export function RolloutDetails({
}) {
const router = useRouter();
const config = STATUS_CONFIG[rollout.status as RolloutStatus];
const Icon = config.icon;
const isLive =
rollout.status === "queued" || rollout.status === "in_progress";
const isAnimated = rollout.status === "in_progress";
Expand All @@ -109,12 +103,12 @@ export function RolloutDetails({
</Button>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
<span
className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium ${config.color} ${config.bgColor}`}
>
<Icon className={`size-4 ${isAnimated ? "animate-spin" : ""}`} />
{config.label}
</span>
<StatusBadge
icon={config.icon}
label={config.label}
isAnimated={isAnimated}
className={config.color}
/>
{rollout.currentStage && isLive && (
<span className="text-sm text-muted-foreground">
{formatStage(rollout.currentStage)}
Expand Down
Loading
Loading