feat: align Signage with platform display and design

This commit is contained in:
ekklesia
2026-09-30 18:21:42 -05:00
parent af0f0adadc
commit 1bf89a3942
9 changed files with 3043 additions and 2386 deletions
+102 -87
View File
@@ -1,13 +1,13 @@
import type { ModuleUiHostContext } from "@ekklesia/module-ui-sdk";
import {
Badge,
EmptyState,
Panel,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Badge,
EmptyState,
Panel,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "@ekklesia/ui";
import type { ReactElement } from "react";
import type { SignageApi } from "../api.js";
@@ -31,86 +31,101 @@ const SLIDE_RENDERER_CAPABILITY = "signage.slide-renderer";
* exactly that, which is the difference between "this is broken" and "this needs an update".
*/
export function ProvidersPanel({
api,
context,
settings,
api,
context,
settings,
}: {
readonly api: SignageApi;
readonly context: ModuleUiHostContext;
readonly settings: SettingsResponse;
readonly api: SignageApi;
readonly context: ModuleUiHostContext;
readonly settings: SettingsResponse;
}): ReactElement {
const providers = useResource(
() => api.get<{ providers: readonly ProviderHealth[] }>("providers"),
[],
);
const published = new Set(
context.contributions.list(SLIDE_RENDERER_CAPABILITY).map((contribution) => contribution.id),
);
const providers = useResource(
() => api.get<{ providers: readonly ProviderHealth[] }>("providers"),
[],
);
const published = new Set(
context.contributions
.list(SLIDE_RENDERER_CAPABILITY)
.map((contribution) => contribution.id),
);
return (
<Panel
description={`Renderer input contract ${settings.rendererInputVersion}. Only providers that are actually running appear here.`}
title="Content providers"
>
{renderLoadable(
providers.state,
(value) =>
value.providers.length === 0 ? (
<EmptyState
description="Signage works on its own with native images. Install a content module to add more."
title="No content providers registered"
/>
) : (
<Table label="Provider health">
<TableHead>
<TableRow>
<TableCell header>Provider</TableCell>
<TableCell header>Contract</TableCell>
<TableCell header>Renderer</TableCell>
<TableCell header>Health</TableCell>
<TableCell header>Last success</TableCell>
</TableRow>
</TableHead>
<TableBody>
{value.providers.map((provider) => {
const rendererPublished = published.has(provider.renderer.contributionId);
const isNative = provider.renderer.contributionId === "signage.native-image";
return (
<TableRow key={provider.providerId}>
<TableCell>
<span className="signage-provider__id">{provider.providerId}</span>
<span className="signage-provider__version">{`v${provider.providerVersion}`}</span>
</TableCell>
<TableCell>{provider.contractVersion}</TableCell>
<TableCell>
{isNative ? (
<Badge tone="success">Signage renders this</Badge>
) : rendererPublished ? (
<Badge tone="success">{provider.renderer.contributionId}</Badge>
) : (
<Badge tone="warning">Renderer not published</Badge>
)}
</TableCell>
<TableCell>
{provider.consecutiveFailures === 0 ? (
<Badge tone="success">Healthy</Badge>
) : (
<Badge tone="danger">
{`${provider.consecutiveFailures} failures · ${provider.lastFailureCode ?? "unknown"}`}
</Badge>
)}
</TableCell>
<TableCell>
{formatInstant(provider.lastSuccessAt, settings.timezone)}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
),
{ label: "Loading providers…", onRetry: providers.reload },
)}
</Panel>
);
return (
<Panel
actions={
<Badge tone="neutral">{`Contract ${settings.rendererInputVersion}`}</Badge>
}
description="Running content sources available to timelines."
title="Content providers"
>
{renderLoadable(
providers.state,
(value) =>
value.providers.length === 0 ? (
<EmptyState
description="Signage works on its own with native images. Install a content module to add more."
title="No content providers registered"
/>
) : (
<Table label="Provider health">
<TableHead>
<TableRow>
<TableCell header>Provider</TableCell>
<TableCell header>Contract</TableCell>
<TableCell header>Renderer</TableCell>
<TableCell header>Health</TableCell>
<TableCell header>Last success</TableCell>
</TableRow>
</TableHead>
<TableBody>
{value.providers.map((provider) => {
const rendererPublished = published.has(
provider.renderer.contributionId,
);
const isNative =
provider.renderer.contributionId === "signage.native-image";
return (
<TableRow key={provider.providerId}>
<TableCell>
<span className="signage-provider__id">
{provider.providerId}
</span>
<span className="signage-provider__version">{`v${provider.providerVersion}`}</span>
</TableCell>
<TableCell>{provider.contractVersion}</TableCell>
<TableCell>
{isNative ? (
<Badge tone="info">Built in</Badge>
) : rendererPublished ? (
<Badge tone="success">
{provider.renderer.contributionId}
</Badge>
) : (
<Badge tone="warning">Renderer not published</Badge>
)}
</TableCell>
<TableCell>
{provider.consecutiveFailures === 0 ? (
<Badge tone="success">Healthy</Badge>
) : (
<Badge tone="danger">
{`${provider.consecutiveFailures} failures · ${provider.lastFailureCode ?? "unknown"}`}
</Badge>
)}
</TableCell>
<TableCell>
{formatInstant(
provider.lastSuccessAt,
settings.timezone,
)}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
),
{ label: "Loading providers…", onRetry: providers.reload },
)}
</Panel>
);
}