openclaw/ui/src/ui/views/connections.ts
2026-01-03 03:07:13 +01:00

1008 lines
35 KiB
TypeScript

import { html, nothing } from "lit";
import { formatAgo } from "../format";
import type { ProvidersStatusSnapshot } from "../types";
import type { DiscordForm, IMessageForm, SignalForm, TelegramForm } from "../ui-types";
export type ConnectionsProps = {
connected: boolean;
loading: boolean;
snapshot: ProvidersStatusSnapshot | null;
lastError: string | null;
lastSuccessAt: number | null;
whatsappMessage: string | null;
whatsappQrDataUrl: string | null;
whatsappConnected: boolean | null;
whatsappBusy: boolean;
telegramForm: TelegramForm;
telegramTokenLocked: boolean;
telegramSaving: boolean;
telegramStatus: string | null;
discordForm: DiscordForm;
discordTokenLocked: boolean;
discordSaving: boolean;
discordStatus: string | null;
signalForm: SignalForm;
signalSaving: boolean;
signalStatus: string | null;
imessageForm: IMessageForm;
imessageSaving: boolean;
imessageStatus: string | null;
onRefresh: (probe: boolean) => void;
onWhatsAppStart: (force: boolean) => void;
onWhatsAppWait: () => void;
onWhatsAppLogout: () => void;
onTelegramChange: (patch: Partial<TelegramForm>) => void;
onTelegramSave: () => void;
onDiscordChange: (patch: Partial<DiscordForm>) => void;
onDiscordSave: () => void;
onSignalChange: (patch: Partial<SignalForm>) => void;
onSignalSave: () => void;
onIMessageChange: (patch: Partial<IMessageForm>) => void;
onIMessageSave: () => void;
};
export function renderConnections(props: ConnectionsProps) {
const whatsapp = props.snapshot?.whatsapp;
const telegram = props.snapshot?.telegram;
const discord = props.snapshot?.discord ?? null;
const signal = props.snapshot?.signal ?? null;
const imessage = props.snapshot?.imessage ?? null;
const providerOrder: ProviderKey[] = [
"whatsapp",
"telegram",
"discord",
"signal",
"imessage",
];
const orderedProviders = providerOrder
.map((key, index) => ({
key,
enabled: providerEnabled(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">
${orderedProviders.map((provider) =>
renderProvider(provider.key, props, { whatsapp, telegram, discord, signal, imessage }),
)}
</section>
<section class="card" style="margin-top: 18px;">
<div class="row" style="justify-content: space-between;">
<div>
<div class="card-title">Connection health</div>
<div class="card-sub">Provider 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 formatDuration(ms?: number | null) {
if (!ms && ms !== 0) return "n/a";
const sec = Math.round(ms / 1000);
if (sec < 60) return `${sec}s`;
const min = Math.round(sec / 60);
if (min < 60) return `${min}m`;
const hr = Math.round(min / 60);
return `${hr}h`;
}
type ProviderKey = "whatsapp" | "telegram" | "discord" | "signal" | "imessage";
function providerEnabled(key: ProviderKey, props: ConnectionsProps) {
const snapshot = props.snapshot;
if (!snapshot) return false;
switch (key) {
case "whatsapp":
return (
snapshot.whatsapp.configured ||
snapshot.whatsapp.linked ||
snapshot.whatsapp.running
);
case "telegram":
return snapshot.telegram.configured || snapshot.telegram.running;
case "discord":
return Boolean(snapshot.discord?.configured || snapshot.discord?.running);
case "signal":
return Boolean(snapshot.signal?.configured || snapshot.signal?.running);
case "imessage":
return Boolean(snapshot.imessage?.configured || snapshot.imessage?.running);
default:
return false;
}
}
function renderProvider(
key: ProviderKey,
props: ConnectionsProps,
data: {
whatsapp?: ProvidersStatusSnapshot["whatsapp"];
telegram?: ProvidersStatusSnapshot["telegram"];
discord?: ProvidersStatusSnapshot["discord"] | null;
signal?: ProvidersStatusSnapshot["signal"] | null;
imessage?: ProvidersStatusSnapshot["imessage"] | null;
},
) {
switch (key) {
case "whatsapp": {
const whatsapp = data.whatsapp;
return html`
<div class="card">
<div class="card-title">WhatsApp</div>
<div class="card-sub">Link WhatsApp Web and monitor connection health.</div>
<div class="status-list" style="margin-top: 16px;">
<div>
<span class="label">Configured</span>
<span>${whatsapp?.configured ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Linked</span>
<span>${whatsapp?.linked ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Running</span>
<span>${whatsapp?.running ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Connected</span>
<span>${whatsapp?.connected ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Last connect</span>
<span>
${whatsapp?.lastConnectedAt
? formatAgo(whatsapp.lastConnectedAt)
: "n/a"}
</span>
</div>
<div>
<span class="label">Last message</span>
<span>
${whatsapp?.lastMessageAt ? formatAgo(whatsapp.lastMessageAt) : "n/a"}
</span>
</div>
<div>
<span class="label">Auth age</span>
<span>
${whatsapp?.authAgeMs != null ? formatDuration(whatsapp.authAgeMs) : "n/a"}
</span>
</div>
</div>
${whatsapp?.lastError
? html`<div class="callout danger" style="margin-top: 12px;">
${whatsapp.lastError}
</div>`
: nothing}
${props.whatsappMessage
? html`<div class="callout" style="margin-top: 12px;">
${props.whatsappMessage}
</div>`
: nothing}
${props.whatsappQrDataUrl
? html`<div class="qr-wrap">
<img src=${props.whatsappQrDataUrl} alt="WhatsApp QR" />
</div>`
: nothing}
<div class="row" style="margin-top: 14px; flex-wrap: wrap;">
<button
class="btn primary"
?disabled=${props.whatsappBusy}
@click=${() => props.onWhatsAppStart(false)}
>
${props.whatsappBusy ? "Working…" : "Show QR"}
</button>
<button
class="btn"
?disabled=${props.whatsappBusy}
@click=${() => props.onWhatsAppStart(true)}
>
Relink
</button>
<button
class="btn"
?disabled=${props.whatsappBusy}
@click=${() => props.onWhatsAppWait()}
>
Wait for scan
</button>
<button
class="btn danger"
?disabled=${props.whatsappBusy}
@click=${() => props.onWhatsAppLogout()}
>
Logout
</button>
<button class="btn" @click=${() => props.onRefresh(true)}>
Refresh
</button>
</div>
</div>
`;
}
case "telegram": {
const telegram = data.telegram;
return html`
<div class="card">
<div class="card-title">Telegram</div>
<div class="card-sub">Bot token and delivery options.</div>
<div class="status-list" style="margin-top: 16px;">
<div>
<span class="label">Configured</span>
<span>${telegram?.configured ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Running</span>
<span>${telegram?.running ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Mode</span>
<span>${telegram?.mode ?? "n/a"}</span>
</div>
<div>
<span class="label">Last start</span>
<span>${telegram?.lastStartAt ? formatAgo(telegram.lastStartAt) : "n/a"}</span>
</div>
<div>
<span class="label">Last probe</span>
<span>${telegram?.lastProbeAt ? formatAgo(telegram.lastProbeAt) : "n/a"}</span>
</div>
</div>
${telegram?.lastError
? html`<div class="callout danger" style="margin-top: 12px;">
${telegram.lastError}
</div>`
: nothing}
${telegram?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${telegram.probe.ok ? "ok" : "failed"} ·
${telegram.probe.status ?? ""}
${telegram.probe.error ?? ""}
</div>`
: nothing}
<div class="form-grid" style="margin-top: 16px;">
<label class="field">
<span>Bot token</span>
<input
type="password"
.value=${props.telegramForm.token}
?disabled=${props.telegramTokenLocked}
@input=${(e: Event) =>
props.onTelegramChange({
token: (e.target as HTMLInputElement).value,
})}
/>
</label>
<label class="field">
<span>Require mention in groups</span>
<select
.value=${props.telegramForm.requireMention ? "yes" : "no"}
@change=${(e: Event) =>
props.onTelegramChange({
requireMention: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>Allow from</span>
<input
.value=${props.telegramForm.allowFrom}
@input=${(e: Event) =>
props.onTelegramChange({
allowFrom: (e.target as HTMLInputElement).value,
})}
placeholder="123456789, @team"
/>
</label>
<label class="field">
<span>Proxy</span>
<input
.value=${props.telegramForm.proxy}
@input=${(e: Event) =>
props.onTelegramChange({
proxy: (e.target as HTMLInputElement).value,
})}
placeholder="socks5://localhost:9050"
/>
</label>
<label class="field">
<span>Webhook URL</span>
<input
.value=${props.telegramForm.webhookUrl}
@input=${(e: Event) =>
props.onTelegramChange({
webhookUrl: (e.target as HTMLInputElement).value,
})}
placeholder="https://example.com/telegram-webhook"
/>
</label>
<label class="field">
<span>Webhook secret</span>
<input
.value=${props.telegramForm.webhookSecret}
@input=${(e: Event) =>
props.onTelegramChange({
webhookSecret: (e.target as HTMLInputElement).value,
})}
placeholder="secret"
/>
</label>
<label class="field">
<span>Webhook path</span>
<input
.value=${props.telegramForm.webhookPath}
@input=${(e: Event) =>
props.onTelegramChange({
webhookPath: (e.target as HTMLInputElement).value,
})}
placeholder="/telegram-webhook"
/>
</label>
</div>
${props.telegramTokenLocked
? html`<div class="callout" style="margin-top: 12px;">
TELEGRAM_BOT_TOKEN is set in the environment. Config edits will not override it.
</div>`
: nothing}
${props.telegramStatus
? html`<div class="callout" style="margin-top: 12px;">
${props.telegramStatus}
</div>`
: nothing}
<div class="row" style="margin-top: 14px;">
<button
class="btn primary"
?disabled=${props.telegramSaving}
@click=${() => props.onTelegramSave()}
>
${props.telegramSaving ? "Saving…" : "Save"}
</button>
<button class="btn" @click=${() => props.onRefresh(true)}>
Probe
</button>
</div>
</div>
`;
}
case "discord": {
const discord = data.discord;
const botName = discord?.probe?.bot?.username;
return html`
<div class="card">
<div class="card-title">Discord</div>
<div class="card-sub">Bot connection and probe status.</div>
<div class="status-list" style="margin-top: 16px;">
<div>
<span class="label">Configured</span>
<span>${discord?.configured ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Running</span>
<span>${discord?.running ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Bot</span>
<span>${botName ? `@${botName}` : "n/a"}</span>
</div>
<div>
<span class="label">Last start</span>
<span>${discord?.lastStartAt ? formatAgo(discord.lastStartAt) : "n/a"}</span>
</div>
<div>
<span class="label">Last probe</span>
<span>${discord?.lastProbeAt ? formatAgo(discord.lastProbeAt) : "n/a"}</span>
</div>
</div>
${discord?.lastError
? html`<div class="callout danger" style="margin-top: 12px;">
${discord.lastError}
</div>`
: nothing}
${discord?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${discord.probe.ok ? "ok" : "failed"} ·
${discord.probe.status ?? ""}
${discord.probe.error ?? ""}
</div>`
: nothing}
<div class="form-grid" style="margin-top: 16px;">
<label class="field">
<span>Enabled</span>
<select
.value=${props.discordForm.enabled ? "yes" : "no"}
@change=${(e: Event) =>
props.onDiscordChange({
enabled: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>Bot token</span>
<input
type="password"
.value=${props.discordForm.token}
?disabled=${props.discordTokenLocked}
@input=${(e: Event) =>
props.onDiscordChange({
token: (e.target as HTMLInputElement).value,
})}
/>
</label>
<label class="field">
<span>Allow DMs from</span>
<input
.value=${props.discordForm.allowFrom}
@input=${(e: Event) =>
props.onDiscordChange({
allowFrom: (e.target as HTMLInputElement).value,
})}
placeholder="123456789, username#1234"
/>
</label>
<label class="field">
<span>Group DMs</span>
<select
.value=${props.discordForm.groupEnabled ? "yes" : "no"}
@change=${(e: Event) =>
props.onDiscordChange({
groupEnabled: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Enabled</option>
<option value="no">Disabled</option>
</select>
</label>
<label class="field">
<span>Group channels</span>
<input
.value=${props.discordForm.groupChannels}
@input=${(e: Event) =>
props.onDiscordChange({
groupChannels: (e.target as HTMLInputElement).value,
})}
placeholder="channelId1, channelId2"
/>
</label>
<label class="field">
<span>Media max MB</span>
<input
.value=${props.discordForm.mediaMaxMb}
@input=${(e: Event) =>
props.onDiscordChange({
mediaMaxMb: (e.target as HTMLInputElement).value,
})}
placeholder="8"
/>
</label>
<label class="field">
<span>History limit</span>
<input
.value=${props.discordForm.historyLimit}
@input=${(e: Event) =>
props.onDiscordChange({
historyLimit: (e.target as HTMLInputElement).value,
})}
placeholder="20"
/>
</label>
<label class="field">
<span>Slash command</span>
<select
.value=${props.discordForm.slashEnabled ? "yes" : "no"}
@change=${(e: Event) =>
props.onDiscordChange({
slashEnabled: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Enabled</option>
<option value="no">Disabled</option>
</select>
</label>
<label class="field">
<span>Slash name</span>
<input
.value=${props.discordForm.slashName}
@input=${(e: Event) =>
props.onDiscordChange({
slashName: (e.target as HTMLInputElement).value,
})}
placeholder="clawd"
/>
</label>
<label class="field">
<span>Slash session prefix</span>
<input
.value=${props.discordForm.slashSessionPrefix}
@input=${(e: Event) =>
props.onDiscordChange({
slashSessionPrefix: (e.target as HTMLInputElement).value,
})}
placeholder="discord:slash"
/>
</label>
<label class="field">
<span>Slash ephemeral</span>
<select
.value=${props.discordForm.slashEphemeral ? "yes" : "no"}
@change=${(e: Event) =>
props.onDiscordChange({
slashEphemeral: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
</div>
${props.discordTokenLocked
? html`<div class="callout" style="margin-top: 12px;">
DISCORD_BOT_TOKEN is set in the environment. Config edits will not override it.
</div>`
: nothing}
${props.discordStatus
? html`<div class="callout" style="margin-top: 12px;">
${props.discordStatus}
</div>`
: nothing}
<div class="row" style="margin-top: 14px;">
<button
class="btn primary"
?disabled=${props.discordSaving}
@click=${() => props.onDiscordSave()}
>
${props.discordSaving ? "Saving…" : "Save"}
</button>
<button class="btn" @click=${() => props.onRefresh(true)}>
Probe
</button>
</div>
</div>
`;
}
case "signal": {
const signal = data.signal;
return html`
<div class="card">
<div class="card-title">Signal</div>
<div class="card-sub">REST daemon status and probe details.</div>
<div class="status-list" style="margin-top: 16px;">
<div>
<span class="label">Configured</span>
<span>${signal?.configured ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Running</span>
<span>${signal?.running ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Base URL</span>
<span>${signal?.baseUrl ?? "n/a"}</span>
</div>
<div>
<span class="label">Last start</span>
<span>${signal?.lastStartAt ? formatAgo(signal.lastStartAt) : "n/a"}</span>
</div>
<div>
<span class="label">Last probe</span>
<span>${signal?.lastProbeAt ? formatAgo(signal.lastProbeAt) : "n/a"}</span>
</div>
</div>
${signal?.lastError
? html`<div class="callout danger" style="margin-top: 12px;">
${signal.lastError}
</div>`
: nothing}
${signal?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${signal.probe.ok ? "ok" : "failed"} ·
${signal.probe.status ?? ""}
${signal.probe.error ?? ""}
</div>`
: nothing}
<div class="form-grid" style="margin-top: 16px;">
<label class="field">
<span>Enabled</span>
<select
.value=${props.signalForm.enabled ? "yes" : "no"}
@change=${(e: Event) =>
props.onSignalChange({
enabled: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>Account</span>
<input
.value=${props.signalForm.account}
@input=${(e: Event) =>
props.onSignalChange({
account: (e.target as HTMLInputElement).value,
})}
placeholder="+15551234567"
/>
</label>
<label class="field">
<span>HTTP URL</span>
<input
.value=${props.signalForm.httpUrl}
@input=${(e: Event) =>
props.onSignalChange({
httpUrl: (e.target as HTMLInputElement).value,
})}
placeholder="http://127.0.0.1:8080"
/>
</label>
<label class="field">
<span>HTTP host</span>
<input
.value=${props.signalForm.httpHost}
@input=${(e: Event) =>
props.onSignalChange({
httpHost: (e.target as HTMLInputElement).value,
})}
placeholder="127.0.0.1"
/>
</label>
<label class="field">
<span>HTTP port</span>
<input
.value=${props.signalForm.httpPort}
@input=${(e: Event) =>
props.onSignalChange({
httpPort: (e.target as HTMLInputElement).value,
})}
placeholder="8080"
/>
</label>
<label class="field">
<span>CLI path</span>
<input
.value=${props.signalForm.cliPath}
@input=${(e: Event) =>
props.onSignalChange({
cliPath: (e.target as HTMLInputElement).value,
})}
placeholder="signal-cli"
/>
</label>
<label class="field">
<span>Auto start</span>
<select
.value=${props.signalForm.autoStart ? "yes" : "no"}
@change=${(e: Event) =>
props.onSignalChange({
autoStart: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>Receive mode</span>
<select
.value=${props.signalForm.receiveMode}
@change=${(e: Event) =>
props.onSignalChange({
receiveMode: (e.target as HTMLSelectElement).value as
| "on-start"
| "manual"
| "",
})}
>
<option value="">Default</option>
<option value="on-start">on-start</option>
<option value="manual">manual</option>
</select>
</label>
<label class="field">
<span>Ignore attachments</span>
<select
.value=${props.signalForm.ignoreAttachments ? "yes" : "no"}
@change=${(e: Event) =>
props.onSignalChange({
ignoreAttachments:
(e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>Ignore stories</span>
<select
.value=${props.signalForm.ignoreStories ? "yes" : "no"}
@change=${(e: Event) =>
props.onSignalChange({
ignoreStories: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>Send read receipts</span>
<select
.value=${props.signalForm.sendReadReceipts ? "yes" : "no"}
@change=${(e: Event) =>
props.onSignalChange({
sendReadReceipts:
(e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>Allow from</span>
<input
.value=${props.signalForm.allowFrom}
@input=${(e: Event) =>
props.onSignalChange({
allowFrom: (e.target as HTMLInputElement).value,
})}
placeholder="12345, +1555"
/>
</label>
<label class="field">
<span>Media max MB</span>
<input
.value=${props.signalForm.mediaMaxMb}
@input=${(e: Event) =>
props.onSignalChange({
mediaMaxMb: (e.target as HTMLInputElement).value,
})}
placeholder="8"
/>
</label>
</div>
${props.signalStatus
? html`<div class="callout" style="margin-top: 12px;">
${props.signalStatus}
</div>`
: nothing}
<div class="row" style="margin-top: 14px;">
<button
class="btn primary"
?disabled=${props.signalSaving}
@click=${() => props.onSignalSave()}
>
${props.signalSaving ? "Saving…" : "Save"}
</button>
<button class="btn" @click=${() => props.onRefresh(true)}>
Probe
</button>
</div>
</div>
`;
}
case "imessage": {
const imessage = data.imessage;
return html`
<div class="card">
<div class="card-title">iMessage</div>
<div class="card-sub">imsg CLI and database availability.</div>
<div class="status-list" style="margin-top: 16px;">
<div>
<span class="label">Configured</span>
<span>${imessage?.configured ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Running</span>
<span>${imessage?.running ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">CLI</span>
<span>${imessage?.cliPath ?? "n/a"}</span>
</div>
<div>
<span class="label">DB</span>
<span>${imessage?.dbPath ?? "n/a"}</span>
</div>
<div>
<span class="label">Last start</span>
<span>
${imessage?.lastStartAt ? formatAgo(imessage.lastStartAt) : "n/a"}
</span>
</div>
<div>
<span class="label">Last probe</span>
<span>
${imessage?.lastProbeAt ? formatAgo(imessage.lastProbeAt) : "n/a"}
</span>
</div>
</div>
${imessage?.lastError
? html`<div class="callout danger" style="margin-top: 12px;">
${imessage.lastError}
</div>`
: nothing}
${imessage?.probe && !imessage.probe.ok
? html`<div class="callout" style="margin-top: 12px;">
Probe failed · ${imessage.probe.error ?? "unknown error"}
</div>`
: nothing}
<div class="form-grid" style="margin-top: 16px;">
<label class="field">
<span>Enabled</span>
<select
.value=${props.imessageForm.enabled ? "yes" : "no"}
@change=${(e: Event) =>
props.onIMessageChange({
enabled: (e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>CLI path</span>
<input
.value=${props.imessageForm.cliPath}
@input=${(e: Event) =>
props.onIMessageChange({
cliPath: (e.target as HTMLInputElement).value,
})}
placeholder="imsg"
/>
</label>
<label class="field">
<span>DB path</span>
<input
.value=${props.imessageForm.dbPath}
@input=${(e: Event) =>
props.onIMessageChange({
dbPath: (e.target as HTMLInputElement).value,
})}
placeholder="~/Library/Messages/chat.db"
/>
</label>
<label class="field">
<span>Service</span>
<select
.value=${props.imessageForm.service}
@change=${(e: Event) =>
props.onIMessageChange({
service: (e.target as HTMLSelectElement).value as
| "auto"
| "imessage"
| "sms",
})}
>
<option value="auto">Auto</option>
<option value="imessage">iMessage</option>
<option value="sms">SMS</option>
</select>
</label>
<label class="field">
<span>Region</span>
<input
.value=${props.imessageForm.region}
@input=${(e: Event) =>
props.onIMessageChange({
region: (e.target as HTMLInputElement).value,
})}
placeholder="US"
/>
</label>
<label class="field">
<span>Allow from</span>
<input
.value=${props.imessageForm.allowFrom}
@input=${(e: Event) =>
props.onIMessageChange({
allowFrom: (e.target as HTMLInputElement).value,
})}
placeholder="chat_id:101, +1555"
/>
</label>
<label class="field">
<span>Include attachments</span>
<select
.value=${props.imessageForm.includeAttachments ? "yes" : "no"}
@change=${(e: Event) =>
props.onIMessageChange({
includeAttachments:
(e.target as HTMLSelectElement).value === "yes",
})}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</label>
<label class="field">
<span>Media max MB</span>
<input
.value=${props.imessageForm.mediaMaxMb}
@input=${(e: Event) =>
props.onIMessageChange({
mediaMaxMb: (e.target as HTMLInputElement).value,
})}
placeholder="16"
/>
</label>
</div>
${props.imessageStatus
? html`<div class="callout" style="margin-top: 12px;">
${props.imessageStatus}
</div>`
: nothing}
<div class="row" style="margin-top: 14px;">
<button
class="btn primary"
?disabled=${props.imessageSaving}
@click=${() => props.onIMessageSave()}
>
${props.imessageSaving ? "Saving…" : "Save"}
</button>
<button class="btn" @click=${() => props.onRefresh(true)}>
Probe
</button>
</div>
</div>
`;
}
default:
return nothing;
}
}