Merge pull request 'Discord: main DRB bot optional end to end + owner-flow integration tests (#225 S8)' (#271) from feat/225-s8-main-bot-optional into main
This commit was merged in pull request #271.
This commit is contained in:
@@ -329,12 +329,13 @@ export function DiscordServersCard({ canEdit, isAdmin }: { canEdit: boolean; isA
|
||||
|
||||
<div className="px-5 py-3 border-t border-line bg-raised text-xs text-ink-muted">
|
||||
{restricted ? (
|
||||
<>Unlinked servers see only <b className="text-ink">/drb link</b> and <b className="text-ink">/help</b>. </>
|
||||
<>Where the DRB server bot is present, an unlinked server sees only <b className="text-ink">/drb link</b> and <b className="text-ink">/help</b>. </>
|
||||
) : isAdmin ? (
|
||||
<>Platform setting: unlinked servers still act for the default organization (<span className="font-mono">DISCORD_UNLINKED_GUILD_ORG_ID</span>) until linked. </>
|
||||
) : null}
|
||||
Every command and autocomplete is scoped to the linked organization — no server sees another
|
||||
organization's nodes, systems or bots.
|
||||
Radio channels, and every DRB server bot command and autocomplete, are scoped to the linked organization — no
|
||||
server sees another organization's nodes, systems or bots. The DRB server bot is optional: a radio bot
|
||||
alone is enough to stream.
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
// Node page "Discord radio" panel + picker (server-26#213 item 3, #109 item 13).
|
||||
// Replaces the raw Guild ID / Voice Channel ID modal. The picker lists only
|
||||
// the Discord servers linked to this node's org and the voice channels the
|
||||
// DRB bot reported in them; a pick becomes a radio channel pinned to this node
|
||||
// (desired state — the node reconciles and reports back, shown per row).
|
||||
// the Discord servers linked to this node's org and their voice channels, as
|
||||
// read by a radio bot in the server or pushed by the DRB server bot — either
|
||||
// is enough; the server bot is optional (server-26#225). A pick becomes a
|
||||
// radio channel pinned to this node (desired state — the node reconciles and
|
||||
// reports back, shown per row).
|
||||
// A voice channel can carry several radio streams, each its own bot playing
|
||||
// its own system (server-26#213 follow-up): picking a channel that already
|
||||
// plays another system ADDS this node's system beside it; only a stream of
|
||||
@@ -211,8 +213,9 @@ function DiscordPicker({
|
||||
<legend className="text-xs text-ink-muted mb-2">Discord server</legend>
|
||||
{usable.length === 0 ? (
|
||||
<p className="text-xs text-ink-2 bg-raised border border-line rounded-lg px-3 py-2">
|
||||
No Discord server is linked to your organization yet. Add the DRB bot to your Discord server and
|
||||
link it to your organization; it shows up here once the bot reports its channels.
|
||||
No Discord server is linked to your organization yet. The organization owner connects one under
|
||||
Settings → Organization → <b className="text-ink">Connect a Discord server</b>: adding a radio bot
|
||||
to the server links it, and its voice channels show up here.
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
@@ -259,12 +262,13 @@ function DiscordPicker({
|
||||
<legend className="text-xs text-ink-muted mb-2">Voice channel</legend>
|
||||
{!guild.synced ? (
|
||||
<p className="text-xs text-ink-2 bg-raised border border-line rounded-lg px-3 py-2">
|
||||
The DRB bot has not reported this server's channels yet. It does when it starts and every 15
|
||||
minutes — check back shortly.
|
||||
DRB hasn't read this server's voice channels yet. A radio bot in the server lists them
|
||||
(or the DRB server bot, if it's there) — close this and open it again in a moment. If it
|
||||
stays empty, no radio bot is in this server yet — invite one with the link under the server above.
|
||||
</p>
|
||||
) : guild.voice_channels.length === 0 ? (
|
||||
<p className="text-xs text-ink-2 bg-raised border border-line rounded-lg px-3 py-2">
|
||||
This server has no voice channels the DRB bot can see.
|
||||
This server has no voice channels DRB's bots can see.
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -8,8 +8,12 @@ import { DIM, Field, INPUT, Panel, Status, Table, TD } from "./ui";
|
||||
|
||||
// Linked Discord servers = discord_guilds docs, plus any server that has had a
|
||||
// radio channel without being linked (it acts for DISCORD_UNLINKED_GUILD_ORG_ID).
|
||||
// Linking is PUT /discord/guilds/{id}/link (platform admin). The owner-side
|
||||
// "/drb link code:" flow is server-26#213 item 1 and is not built here.
|
||||
// Linking here is PUT /discord/guilds/{id}/link (platform admin, support).
|
||||
// Org owners link their own from Settings → Organization: "Connect a Discord
|
||||
// server" (a radio bot's OAuth invite, no DRB server bot needed) or a
|
||||
// /drb link code (needs the server bot) — server-26#213 item 1, #225.
|
||||
// The Bots column says which bots are in each server: the DRB server bot is
|
||||
// optional (server-26#225 S8), one radio bot is enough to stream.
|
||||
// Remove is DELETE /discord/guilds/{id}: its radio channels are stopped then
|
||||
// deleted, and the server is unlinked with no fallback org.
|
||||
|
||||
@@ -85,6 +89,30 @@ function LinkForm({
|
||||
);
|
||||
}
|
||||
|
||||
// Which bots are in the server. The DRB server bot is optional (server-26#225):
|
||||
// "not there" is only a problem when no radio bot is either.
|
||||
function BotsCell({ guild }: { guild: DiscordGuild }) {
|
||||
const radio = guild.radio_bots ?? [];
|
||||
const main = guild.main_bot_present;
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{radio.length > 0 ? (
|
||||
<span title={radio.join(", ")}>
|
||||
{radio.length} radio bot{radio.length === 1 ? "" : "s"}
|
||||
</span>
|
||||
) : (
|
||||
<span className={main ? DIM : "text-sev-moderate"}>no radio bot</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={DIM}>
|
||||
DRB server bot: {main === true ? "present" : main === false ? "not there" : "never seen"}
|
||||
{guild.directory_source === "radio_bot" && " · channels via radio bot"}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type RemoveResult = { guild: string; streams: number; bots: { name: string; left: boolean; error: string | null }[] };
|
||||
|
||||
function RemoveForm({
|
||||
@@ -131,7 +159,7 @@ function RemoveForm({
|
||||
</span>
|
||||
</label>
|
||||
<p className="text-ink-muted">
|
||||
The DRB bot itself stays in the Discord server until someone kicks it there.
|
||||
The DRB server bot, if it is in the Discord server, stays until someone kicks it there.
|
||||
</p>
|
||||
{error && <p className="text-sev-major">{error}</p>}
|
||||
<div className="flex gap-2">
|
||||
@@ -171,8 +199,9 @@ export function ServersPanel({
|
||||
fallback ? (
|
||||
<>
|
||||
<b className="text-ink">Unlinked servers act for {fallbackName}</b> (DISCORD_UNLINKED_GUILD_ORG_ID). Any
|
||||
server the DRB bot is invited to can use that org's systems until it is linked here. Set the variable
|
||||
to an empty string once every real server is linked.
|
||||
server the DRB server bot is invited to can use that org's systems until it is linked here (a radio
|
||||
bot alone never puts an unlinked server here). Set the variable to an empty string once every real server
|
||||
is linked.
|
||||
</>
|
||||
) : (
|
||||
<>Unlinked servers can't use radio commands until they are linked here.</>
|
||||
@@ -189,14 +218,16 @@ export function ServersPanel({
|
||||
{removed.bots.map((b) => `${b.name} ${b.left ? "left" : `did not leave (${b.error ?? "unknown error"})`}`).join("; ")}.
|
||||
</>
|
||||
)}{" "}
|
||||
The DRB bot is still in that Discord server until someone kicks it.
|
||||
The DRB server bot, if it was there, stays in that Discord server until someone kicks it.
|
||||
<button className="ml-3 text-accent hover:underline" onClick={() => setRemoved(null)}>Dismiss</button>
|
||||
</div>
|
||||
)}
|
||||
{overview.guilds.length === 0 ? (
|
||||
<p className="px-4 py-6 text-sm text-ink-muted">No Discord servers yet. Link one, or run a radio command in a server the DRB bot is in.</p>
|
||||
<p className="px-4 py-6 text-sm text-ink-muted">
|
||||
No Discord servers yet. Link one here, or an organization owner connects one under Settings → Organization.
|
||||
</p>
|
||||
) : (
|
||||
<Table head={["Server", "Acts for", "Radio channels", "Linked", "Status", ""]}>
|
||||
<Table head={["Server", "Acts for", "Bots", "Radio channels", "Linked", "Status", ""]}>
|
||||
{overview.guilds.flatMap((g) => {
|
||||
const row = (
|
||||
<tr key={g.guild_id}>
|
||||
@@ -208,6 +239,9 @@ export function ServersPanel({
|
||||
{g.acting_org_name || g.acting_org_id || <span className={DIM}>nobody</span>}
|
||||
{!g.linked && g.acting_org_id && <div className={DIM}>fallback, not linked</div>}
|
||||
</td>
|
||||
<td className={TD}>
|
||||
<BotsCell guild={g} />
|
||||
</td>
|
||||
<td className={TD}>
|
||||
{g.live_streams} / {max}
|
||||
</td>
|
||||
@@ -251,7 +285,7 @@ export function ServersPanel({
|
||||
return [
|
||||
row,
|
||||
<tr key={`${g.guild_id}-remove`}>
|
||||
<td colSpan={6} className="p-0 border-b border-line">
|
||||
<td colSpan={7} className="p-0 border-b border-line">
|
||||
<RemoveForm
|
||||
guild={g}
|
||||
onCancel={() => setRemoving(null)}
|
||||
@@ -269,7 +303,7 @@ export function ServersPanel({
|
||||
return [
|
||||
row,
|
||||
<tr key={`${g.guild_id}-edit`}>
|
||||
<td colSpan={6} className="p-0 border-b border-line">
|
||||
<td colSpan={7} className="p-0 border-b border-line">
|
||||
<LinkForm overview={overview} guild={g} onDone={done} onCancel={() => setEditing(null)} />
|
||||
</td>
|
||||
</tr>,
|
||||
|
||||
@@ -348,7 +348,8 @@ export function StreamsPanel({
|
||||
)}
|
||||
{overview.streams.length === 0 ? (
|
||||
<p className="px-4 py-6 text-sm text-ink-muted">
|
||||
No radio channels yet. Start one here, or run <span className="font-mono">/join</span> in a linked Discord server.
|
||||
No radio channels yet. Start one here or from a node's Discord radio panel, or run{" "}
|
||||
<span className="font-mono">/join</span> where the DRB server bot is in a linked Discord server.
|
||||
</p>
|
||||
) : (
|
||||
<Table head={["Server · channel", "Source", "Talkgroups", "Mode", "Desired → actual", "Bot", ""]}>
|
||||
|
||||
Reference in New Issue
Block a user