237 lines
7.4 KiB
TypeScript
237 lines
7.4 KiB
TypeScript
import { html, nothing } from "lit";
|
|
|
|
import { formatAgo } from "../format";
|
|
import type {
|
|
ChannelAccountSnapshot,
|
|
ChannelsStatusSnapshot,
|
|
DiscordStatus,
|
|
IMessageStatus,
|
|
SignalStatus,
|
|
SlackStatus,
|
|
TelegramStatus,
|
|
WhatsAppStatus,
|
|
} from "../types";
|
|
import type {
|
|
ChannelKey,
|
|
ChannelsChannelData,
|
|
ChannelsProps,
|
|
} from "./channels.types";
|
|
import { channelEnabled, renderChannelAccountCount } from "./channels.shared";
|
|
import { renderChannelConfigSection } from "./channels.config";
|
|
import { renderDiscordCard } from "./channels.discord";
|
|
import { renderIMessageCard } from "./channels.imessage";
|
|
import { renderSignalCard } from "./channels.signal";
|
|
import { renderSlackCard } from "./channels.slack";
|
|
import { renderTelegramCard } from "./channels.telegram";
|
|
import { renderWhatsAppCard } from "./channels.whatsapp";
|
|
|
|
export function renderChannels(props: ChannelsProps) {
|
|
const channels = props.snapshot?.channels as Record<string, unknown> | null;
|
|
const whatsapp = (channels?.whatsapp ?? undefined) as
|
|
| WhatsAppStatus
|
|
| undefined;
|
|
const telegram = (channels?.telegram ?? undefined) as
|
|
| TelegramStatus
|
|
| undefined;
|
|
const discord = (channels?.discord ?? null) as DiscordStatus | null;
|
|
const slack = (channels?.slack ?? null) as SlackStatus | null;
|
|
const signal = (channels?.signal ?? null) as SignalStatus | null;
|
|
const imessage = (channels?.imessage ?? null) as IMessageStatus | null;
|
|
const channelOrder = resolveChannelOrder(props.snapshot);
|
|
const orderedChannels = channelOrder
|
|
.map((key, index) => ({
|
|
key,
|
|
enabled: channelEnabled(key, props),
|
|
order: index,
|
|
}))
|
|
.sort((a, b) => {
|
|
if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
|
|
return a.order - b.order;
|
|
});
|
|
|
|
return html`
|
|
<section class="grid grid-cols-2">
|
|
${orderedChannels.map((channel) =>
|
|
renderChannel(channel.key, props, {
|
|
whatsapp,
|
|
telegram,
|
|
discord,
|
|
slack,
|
|
signal,
|
|
imessage,
|
|
channelAccounts: props.snapshot?.channelAccounts ?? null,
|
|
}),
|
|
)}
|
|
</section>
|
|
|
|
<section class="card" style="margin-top: 18px;">
|
|
<div class="row" style="justify-content: space-between;">
|
|
<div>
|
|
<div class="card-title">Channel health</div>
|
|
<div class="card-sub">Channel status snapshots from the gateway.</div>
|
|
</div>
|
|
<div class="muted">${props.lastSuccessAt ? formatAgo(props.lastSuccessAt) : "n/a"}</div>
|
|
</div>
|
|
${props.lastError
|
|
? html`<div class="callout danger" style="margin-top: 12px;">
|
|
${props.lastError}
|
|
</div>`
|
|
: nothing}
|
|
<pre class="code-block" style="margin-top: 12px;">
|
|
${props.snapshot ? JSON.stringify(props.snapshot, null, 2) : "No snapshot yet."}
|
|
</pre>
|
|
</section>
|
|
`;
|
|
}
|
|
|
|
function resolveChannelOrder(snapshot: ChannelsStatusSnapshot | null): ChannelKey[] {
|
|
if (snapshot?.channelOrder?.length) {
|
|
return snapshot.channelOrder;
|
|
}
|
|
return ["whatsapp", "telegram", "discord", "slack", "signal", "imessage", "bluebubbles"];
|
|
}
|
|
|
|
function renderChannel(
|
|
key: ChannelKey,
|
|
props: ChannelsProps,
|
|
data: ChannelsChannelData,
|
|
) {
|
|
const accountCountLabel = renderChannelAccountCount(
|
|
key,
|
|
data.channelAccounts,
|
|
);
|
|
switch (key) {
|
|
case "whatsapp":
|
|
return renderWhatsAppCard({
|
|
props,
|
|
whatsapp: data.whatsapp,
|
|
accountCountLabel,
|
|
});
|
|
case "telegram":
|
|
return renderTelegramCard({
|
|
props,
|
|
telegram: data.telegram,
|
|
telegramAccounts: data.channelAccounts?.telegram ?? [],
|
|
accountCountLabel,
|
|
});
|
|
case "discord":
|
|
return renderDiscordCard({
|
|
props,
|
|
discord: data.discord,
|
|
accountCountLabel,
|
|
});
|
|
case "slack":
|
|
return renderSlackCard({
|
|
props,
|
|
slack: data.slack,
|
|
accountCountLabel,
|
|
});
|
|
case "signal":
|
|
return renderSignalCard({
|
|
props,
|
|
signal: data.signal,
|
|
accountCountLabel,
|
|
});
|
|
case "imessage":
|
|
return renderIMessageCard({
|
|
props,
|
|
imessage: data.imessage,
|
|
accountCountLabel,
|
|
});
|
|
default:
|
|
return renderGenericChannelCard(key, props, data.channelAccounts ?? {});
|
|
}
|
|
}
|
|
|
|
function renderGenericChannelCard(
|
|
key: ChannelKey,
|
|
props: ChannelsProps,
|
|
channelAccounts: Record<string, ChannelAccountSnapshot[]>,
|
|
) {
|
|
const label = props.snapshot?.channelLabels?.[key] ?? key;
|
|
const status = props.snapshot?.channels?.[key] as Record<string, unknown> | undefined;
|
|
const configured = typeof status?.configured === "boolean" ? status.configured : undefined;
|
|
const running = typeof status?.running === "boolean" ? status.running : undefined;
|
|
const connected = typeof status?.connected === "boolean" ? status.connected : undefined;
|
|
const lastError = typeof status?.lastError === "string" ? status.lastError : undefined;
|
|
const accounts = channelAccounts[key] ?? [];
|
|
const accountCountLabel = renderChannelAccountCount(key, channelAccounts);
|
|
|
|
return html`
|
|
<div class="card">
|
|
<div class="card-title">${label}</div>
|
|
<div class="card-sub">Channel status and configuration.</div>
|
|
${accountCountLabel}
|
|
|
|
${accounts.length > 0
|
|
? html`
|
|
<div class="account-card-list">
|
|
${accounts.map((account) => renderGenericAccount(account))}
|
|
</div>
|
|
`
|
|
: html`
|
|
<div class="status-list" style="margin-top: 16px;">
|
|
<div>
|
|
<span class="label">Configured</span>
|
|
<span>${configured == null ? "n/a" : configured ? "Yes" : "No"}</span>
|
|
</div>
|
|
<div>
|
|
<span class="label">Running</span>
|
|
<span>${running == null ? "n/a" : running ? "Yes" : "No"}</span>
|
|
</div>
|
|
<div>
|
|
<span class="label">Connected</span>
|
|
<span>${connected == null ? "n/a" : connected ? "Yes" : "No"}</span>
|
|
</div>
|
|
</div>
|
|
`}
|
|
|
|
${lastError
|
|
? html`<div class="callout danger" style="margin-top: 12px;">
|
|
${lastError}
|
|
</div>`
|
|
: nothing}
|
|
|
|
${renderChannelConfigSection({ channelId: key, props })}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function renderGenericAccount(account: ChannelAccountSnapshot) {
|
|
return html`
|
|
<div class="account-card">
|
|
<div class="account-card-header">
|
|
<div class="account-card-title">${account.name || account.accountId}</div>
|
|
<div class="account-card-id">${account.accountId}</div>
|
|
</div>
|
|
<div class="status-list account-card-status">
|
|
<div>
|
|
<span class="label">Running</span>
|
|
<span>${account.running ? "Yes" : "No"}</span>
|
|
</div>
|
|
<div>
|
|
<span class="label">Configured</span>
|
|
<span>${account.configured ? "Yes" : "No"}</span>
|
|
</div>
|
|
<div>
|
|
<span class="label">Connected</span>
|
|
<span>
|
|
${account.connected == null ? "n/a" : account.connected ? "Yes" : "No"}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span class="label">Last inbound</span>
|
|
<span>${account.lastInboundAt ? formatAgo(account.lastInboundAt) : "n/a"}</span>
|
|
</div>
|
|
${account.lastError
|
|
? html`
|
|
<div class="account-card-error">
|
|
${account.lastError}
|
|
</div>
|
|
`
|
|
: nothing}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|