132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
import type { ModuleUiHostContext } from "@ekklesia/module-ui-sdk";
|
|
import {
|
|
Badge,
|
|
EmptyState,
|
|
Panel,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableRow,
|
|
} from "@ekklesia/ui";
|
|
import type { ReactElement } from "react";
|
|
import type { SignageApi } from "../api.js";
|
|
import { useResource } from "../hooks.js";
|
|
import type { ProviderHealth, SettingsResponse } from "../types.js";
|
|
import { formatInstant, renderLoadable } from "./shared.js";
|
|
|
|
const SLIDE_RENDERER_CAPABILITY = "signage.slide-renderer";
|
|
|
|
/**
|
|
* Provider health, and the one thing only a browser can tell an operator.
|
|
*
|
|
* A registration is a **running fact**: a provider that was uninstalled or disabled simply is not
|
|
* here, and there is no stored row claiming otherwise. Everything shown is either the provider's own
|
|
* declared identity or a count Signage kept — never a provider's exception, message, or payload.
|
|
*
|
|
* Renderer availability is checked here rather than on the server because the server genuinely
|
|
* cannot know it: whether the platform published a module's contribution is a fact the *host* holds,
|
|
* and `context.contributions` is where it is legible. A provider whose renderer was not published —
|
|
* because the module is missing, disabled, or outside Signage's compatible range — is reported as
|
|
* exactly that, which is the difference between "this is broken" and "this needs an update".
|
|
*/
|
|
export function ProvidersPanel({
|
|
api,
|
|
context,
|
|
settings,
|
|
}: {
|
|
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),
|
|
);
|
|
|
|
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>
|
|
);
|
|
}
|