Compare commits

...

2 Commits

Author SHA1 Message Date
Peter Steinberger
4c151760df fix: show account counts in connections UI (#813) (thanks @dbhurley) 2026-01-13 01:52:36 +00:00
DB Hurley
32c93aefbe feat(ui): display per-account status for multi-account Telegram
When multiple Telegram accounts are configured, the Connections UI now
displays individual status cards for each account showing:
- Bot username and account ID
- Running/configured status
- Last inbound message time
- Per-account errors

Falls back to the existing summary view for single-account configs.
No changes needed to types (already added upstream).
2026-01-13 01:52:36 +00:00
2 changed files with 119 additions and 37 deletions

View File

@ -5,6 +5,7 @@
### Fixes ### Fixes
- Onboarding/Configure: refuse to proceed with invalid configs; run `clawdbot doctor` first to avoid wiping custom fields. (#764 — thanks @mukhtharcm) - Onboarding/Configure: refuse to proceed with invalid configs; run `clawdbot doctor` first to avoid wiping custom fields. (#764 — thanks @mukhtharcm)
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid “Incorrect role information” errors. (#804 — thanks @ThomsenDrake) - Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid “Incorrect role information” errors. (#804 — thanks @ThomsenDrake)
- Connections UI: show account counts for multi-account providers and keep Telegram per-account cards. (#813 — thanks @dbhurley)
- Discord/Slack: centralize reply-thread planning so auto-thread replies stay in the created thread without parent reply refs. - Discord/Slack: centralize reply-thread planning so auto-thread replies stay in the created thread without parent reply refs.
- Update: run `clawdbot doctor --non-interactive` during updates to avoid TTY hangs. (#781 — thanks @ronyrus) - Update: run `clawdbot doctor --non-interactive` during updates to avoid TTY hangs. (#781 — thanks @ronyrus)
- Tools: allow Claude/Gemini tool param aliases (`file_path`, `old_string`, `new_string`) while enforcing required params at runtime. (#793 — thanks @hsrvc) - Tools: allow Claude/Gemini tool param aliases (`file_path`, `old_string`, `new_string`) while enforcing required params at runtime. (#793 — thanks @hsrvc)

View File

@ -4,6 +4,7 @@ import { formatAgo } from "../format";
import type { import type {
DiscordStatus, DiscordStatus,
IMessageStatus, IMessageStatus,
ProviderAccountSnapshot,
ProvidersStatusSnapshot, ProvidersStatusSnapshot,
SignalStatus, SignalStatus,
SlackStatus, SlackStatus,
@ -132,6 +133,7 @@ export function renderConnections(props: ConnectionsProps) {
slack, slack,
signal, signal,
imessage, imessage,
providerAccounts: props.snapshot?.providerAccounts ?? null,
}), }),
)} )}
</section> </section>
@ -206,6 +208,26 @@ function providerEnabled(key: ProviderKey, props: ConnectionsProps) {
} }
} }
function getProviderAccountCount(
key: ProviderKey,
providerAccounts?: Record<string, ProviderAccountSnapshot[]> | null,
): number {
return providerAccounts?.[key]?.length ?? 0;
}
function renderProviderAccountCount(
key: ProviderKey,
providerAccounts?: Record<string, ProviderAccountSnapshot[]> | null,
) {
const count = getProviderAccountCount(key, providerAccounts);
if (count < 2) return nothing;
return html`
<div class="muted" style="margin-top: 8px; font-weight: 500;">
Accounts (${count})
</div>
`;
}
function renderProvider( function renderProvider(
key: ProviderKey, key: ProviderKey,
props: ConnectionsProps, props: ConnectionsProps,
@ -216,8 +238,13 @@ function renderProvider(
slack?: SlackStatus | null; slack?: SlackStatus | null;
signal?: SignalStatus | null; signal?: SignalStatus | null;
imessage?: IMessageStatus | null; imessage?: IMessageStatus | null;
providerAccounts?: Record<string, ProviderAccountSnapshot[]> | null;
}, },
) { ) {
const accountCountLabel = renderProviderAccountCount(
key,
data.providerAccounts,
);
switch (key) { switch (key) {
case "whatsapp": { case "whatsapp": {
const whatsapp = data.whatsapp; const whatsapp = data.whatsapp;
@ -225,6 +252,7 @@ function renderProvider(
<div class="card"> <div class="card">
<div class="card-title">WhatsApp</div> <div class="card-title">WhatsApp</div>
<div class="card-sub">Link WhatsApp Web and monitor connection health.</div> <div class="card-sub">Link WhatsApp Web and monitor connection health.</div>
${accountCountLabel}
<div class="status-list" style="margin-top: 16px;"> <div class="status-list" style="margin-top: 16px;">
<div> <div>
@ -321,11 +349,59 @@ function renderProvider(
} }
case "telegram": { case "telegram": {
const telegram = data.telegram; const telegram = data.telegram;
const telegramAccounts = data.providerAccounts?.telegram ?? [];
const hasMultipleAccounts = telegramAccounts.length > 1;
const renderAccountCard = (account: ProviderAccountSnapshot) => {
const probe =
(account.probe as { bot?: { username?: string } } | undefined) ??
undefined;
const botUsername = probe?.bot?.username;
const label = account.name || account.accountId;
return html`
<div style="border: 1px solid var(--border); border-radius: 6px; padding: 12px; margin-bottom: 8px;">
<div style="font-weight: 500; margin-bottom: 8px;">
${botUsername ? `@${botUsername}` : label}
<span class="muted" style="font-weight: normal;">(${account.accountId})</span>
</div>
<div class="status-list" style="font-size: 13px;">
<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">Last inbound</span>
<span>${account.lastInboundAt ? formatAgo(account.lastInboundAt) : "n/a"}</span>
</div>
${account.lastError
? html`
<div style="color: var(--danger); margin-top: 4px;">
${account.lastError}
</div>
`
: nothing}
</div>
</div>
`;
};
return html` return html`
<div class="card"> <div class="card">
<div class="card-title">Telegram</div> <div class="card-title">Telegram</div>
<div class="card-sub">Bot token and delivery options.</div> <div class="card-sub">Bot token and delivery options.</div>
${accountCountLabel}
${hasMultipleAccounts
? html`
<div style="margin-top: 16px; margin-bottom: 8px;">
${telegramAccounts.map((account) => renderAccountCard(account))}
</div>
`
: html`
<div class="status-list" style="margin-top: 16px;"> <div class="status-list" style="margin-top: 16px;">
<div> <div>
<span class="label">Configured</span> <span class="label">Configured</span>
@ -348,6 +424,7 @@ function renderProvider(
<span>${telegram?.lastProbeAt ? formatAgo(telegram.lastProbeAt) : "n/a"}</span> <span>${telegram?.lastProbeAt ? formatAgo(telegram.lastProbeAt) : "n/a"}</span>
</div> </div>
</div> </div>
`}
${telegram?.lastError ${telegram?.lastError
? html`<div class="callout danger" style="margin-top: 12px;"> ? html`<div class="callout danger" style="margin-top: 12px;">
@ -516,6 +593,7 @@ function renderProvider(
<div class="card"> <div class="card">
<div class="card-title">Discord</div> <div class="card-title">Discord</div>
<div class="card-sub">Bot connection and probe status.</div> <div class="card-sub">Bot connection and probe status.</div>
${accountCountLabel}
<div class="status-list" style="margin-top: 16px;"> <div class="status-list" style="margin-top: 16px;">
<div> <div>
@ -1040,6 +1118,7 @@ function renderProvider(
<div class="card"> <div class="card">
<div class="card-title">Slack</div> <div class="card-title">Slack</div>
<div class="card-sub">Socket mode status and bot details.</div> <div class="card-sub">Socket mode status and bot details.</div>
${accountCountLabel}
<div class="status-list" style="margin-top: 16px;"> <div class="status-list" style="margin-top: 16px;">
<div> <div>
@ -1421,6 +1500,7 @@ function renderProvider(
<div class="card"> <div class="card">
<div class="card-title">Signal</div> <div class="card-title">Signal</div>
<div class="card-sub">REST daemon status and probe details.</div> <div class="card-sub">REST daemon status and probe details.</div>
${accountCountLabel}
<div class="status-list" style="margin-top: 16px;"> <div class="status-list" style="margin-top: 16px;">
<div> <div>
@ -1650,6 +1730,7 @@ function renderProvider(
<div class="card"> <div class="card">
<div class="card-title">iMessage</div> <div class="card-title">iMessage</div>
<div class="card-sub">imsg CLI and database availability.</div> <div class="card-sub">imsg CLI and database availability.</div>
${accountCountLabel}
<div class="status-list" style="margin-top: 16px;"> <div class="status-list" style="margin-top: 16px;">
<div> <div>