Switch to cards

This commit is contained in:
2025-09-06 16:22:22 +01:00
parent 21caaf3901
commit 00c32e7cdf
4 changed files with 68 additions and 156 deletions

View File

@@ -4,188 +4,101 @@ import type { dockerRouterType } from "@/server/api/routers/docker";
import { api } from "@/trpc/react"; import { api } from "@/trpc/react";
import type { JSX } from "react"; import type { JSX } from "react";
function DockerRow({
containerInfo,
}: {
containerInfo: dockerRouterType["list"][number];
}) {
const { data: latest, isError, isLoading } = api.docker.latest.useQuery({ id: containerInfo.container.id }, { refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false });
const outdated = containerInfo.image.current.hash !== latest?.latest.hash;
let latestFragment: JSX.Element | null = null;
if (isError) {
latestFragment = (
<>
<td>{"Error"}</td>
<td>{"Error"}</td>
</>
);
} else if (isLoading) {
latestFragment = (
<>
<td>
<span className="loading loading-dots loading-lg" />
</td>
<td>
<span className="loading loading-dots loading-lg" />
</td>
</>
);
} else if (latest) {
latestFragment = (
<>
<td>{latest?.latest.tag}</td>
<td>{latest?.latest.hash}</td>
</>
);
}
return (
<tr key={containerInfo.container.name} className={`${outdated ? "bg-base-200" : null}`}>
<td className={`border-l-8 ${isLoading ? "border-l-warning/80" : outdated ? "border-l-error/80" : "border-l-info/80"}`}>{containerInfo.container.name}</td>
<td>{containerInfo.image.name}</td>
<td>{containerInfo.image.current.tag}</td>
<td>{containerInfo.image.current.hash}</td>
{latestFragment}
</tr>
);
}
function DockerCard({ function DockerCard({
containerInfo, containerInfo,
}: { }: {
containerInfo: dockerRouterType["list"][number]; containerInfo: dockerRouterType["list"][number];
}) { }) {
const { data: latest, isError, isLoading } = api.docker.latest.useQuery({ id: containerInfo.container.id }, { refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false }); const { data: latest, isError, isLoading, error } = api.docker.latest.useQuery({ id: containerInfo.container.id }, { refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false });
const outdated = containerInfo.image.current.hash !== latest?.latest.hash;
let latestFragment: JSX.Element | null = null; let latestFragment: JSX.Element | null = null;
if (isError) { if (isError) {
latestFragment = ( latestFragment = (
<> <div className="flex flex-row flex-wrap *:basis-1/2">
<td>{"Error"}</td> <dt>Tag</dt>
<td>{"Error"}</td> <dd>Error: {error.message}</dd>
</>
<dt>Short Hash</dt>
<dd>Error: {error.message}</dd>
</div>
); );
} else if (isLoading) { } else if (isLoading) {
latestFragment = ( latestFragment = (
<> <div className="flex flex-row flex-wrap *:basis-1/2">
<td> <dt>Tag</dt>
<span className="loading loading-dots loading-lg" /> <dd className="loading loading-dots loading-sm" />
</td>
<td> <dt>Short Hash</dt>
<span className="loading loading-dots loading-lg" /> <dd className="loading loading-dots loading-sm" />
</td> </div>
</>
); );
} else if (latest) { } else if (latest) {
latestFragment = ( latestFragment = (
<> <div className="flex flex-row flex-wrap *:basis-1/2">
<td>{latest?.latest.tag}</td> <dt>Tag</dt>
<td>{latest?.latest.hash}</td> <dd>{latest?.latest.tag ?? "Unknown"}</dd>
</>
<dt>Short Hash</dt>
<dd>{latest?.latest.hash ?? "Unknown"}</dd>
</div>
); );
} }
const outdated = containerInfo.image.current.hash !== latest?.latest.hash;
const showWarning = isLoading || (!isError && !isLoading && (!latest?.latest.tag || !latest?.latest.hash));
return ( return (
<div key={containerInfo.container.name} className="card card-border w-96 bg-base-100"> <div
<div className="card-body"> key={containerInfo.container.name}
<h2 className="card-title">{containerInfo.container.name}</h2> className={`card card-outline card-compact w-80 border-t-8 bg-base-300 shadow-md ${showWarning ? "border-t-warning/80" : outdated ? "border-t-error/80" : "border-t-info/80"}`}
<div className="stats shadow"> >
<div className="stat"> <dl className="card-body flex flex-col flex-wrap items-center justify-around text-base">
<div className="stat-title">Image</div> <h2 className="text-center text-3xl">{containerInfo.container.name}</h2>
<div className="stat-value">{containerInfo.image.name}</div> <p>{containerInfo.image.name}</p>
</div> <div className="divider divider-primary m-1" />
</div> <h3 className="text-xl underline decoration-1 underline-offset-4">Current</h3>
<div className="stats shadow"> <div className="flex flex-row flex-wrap *:basis-1/2">
<div className="stat"> <dt>Tag</dt>
<div className="stat-title">Tag</div> <dd>{containerInfo.image.current.tag}</dd>
<div className="stat-value">{containerInfo.image.name}</div> <dt>Short Hash</dt>
</div> <dd>{containerInfo.image.current.hash}</dd>
</div> </div>
<div className="stats shadow"> <div className="divider divider-primary m-0" />
<div className="stat"> <h3 className="text-xl underline decoration-1 underline-offset-4">Latest</h3>
<div className="stat-title">Short Hash</div> {latestFragment}
<div className="stat-value">{containerInfo.image.name}</div> </dl>
</div> </div>
</div>
<div className="stats shadow">
<div className="stat">
<div className="stat-title">Tag</div>
<div className="stat-value">{containerInfo.image.name}</div>
</div>
</div>
<div className="stats shadow">
<div className="stat">
<div className="stat-title">Short Hash</div>
<div className="stat-value">{containerInfo.image.name}</div>
</div>
</div>
</div>
</div>
); );
} }
export function DockerTable() { export function DockerTable() {
const { data: list, isLoading: listLoading } = api.docker.list.useQuery(undefined, { refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false }); const { data: list, isLoading: listLoading, error } = api.docker.list.useQuery(undefined, { refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false });
return ( return (
<> <>
{listLoading ? ( {listLoading ? (
<>Loading data...</> <>Loading docker container list...</>
) : ( ) : (
<> <>
<div className="flex flex-row flex-wrap"> <div className={"flex flex-row flex-wrap justify-around gap-8"}>
{list {list ? (
? list list
.sort((ca, cb) => { .sort((ca, cb) => {
if (ca.container.name && cb.container.name) { if (ca.container.name && cb.container.name) {
if (ca.container.name < cb.container.name) { if (ca.container.name < cb.container.name) {
return -1; return -1;
}
if (ca.container.name > cb.container.name) {
return 1;
}
} }
return 0; if (ca.container.name > cb.container.name) {
}) return 1;
.map((containerInfo) => ( }
}
)) return 0;
: null} })
.map((containerInfo) => <DockerCard key={containerInfo.container.name} containerInfo={containerInfo} />)
) : (
<>Error loading docker container list: {error?.message}</>
)}
</div> </div>
{/* <div className={"overflow-x-auto rounded-md border border-base-content/15 bg-base-100"}>
<table className="table-s table-pin-rows table border-separate border-spacing-0">
<thead>
<tr className="border-l-8 border-l-base-200">
<th>Name</th>
<th>Image</th>
<th>Tag</th>
<th>Short Hash</th>
<th>Tag</th>
<th>Short Hash</th>
</tr>
</thead>
<tbody>
{list
? list
.sort((ca, cb) => {
if (ca.container.name && cb.container.name) {
if (ca.container.name < cb.container.name) {
return -1;
}
if (ca.container.name > cb.container.name) {
return 1;
}
}
return 0;
})
.map((containerInfo) => <DockerRow key={containerInfo.container.name} containerInfo={containerInfo} />)
: null}
</tbody>
</table>
</div> */}
</> </>
)} )}
</> </>

View File

@@ -5,10 +5,8 @@ export default async function Home() {
void api.docker.list.prefetch(); void api.docker.list.prefetch();
return ( return (
<main className="flex min-h-screen flex-col items-center justify-center"> <main className="container flex min-h-screen flex-col items-center justify-center px-4 py-8">
<div className="container flex max-h-screen flex-col items-center justify-center px-4 py-8"> <DockerTable />
<DockerTable />
</div>
</main> </main>
); );
} }

View File

@@ -188,6 +188,7 @@ async function getLatest(image: string, tag?: string): Promise<{ hash: string; t
return { return {
hash, hash,
tag,
}; };
} }

View File

@@ -6,4 +6,4 @@
@theme { @theme {
--font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
} }