hive-dashboard: remove the MATRIX credentials tab and its login route
The CR3D3NTIALS page's MATRIX tab was the only caller of `POST /api/matrix-account-login` (provision/log in an external matrix account through the hive) and `GET /api/matrix-accounts` (its account list). External matrix accounts are linked from the swarm UI now (`LinkMatrixAccountForm` -> swarm-controller), so the hive-side UI and both routes go. `priv_client::restart_matrix_daemon` had no other caller and goes with them. Already-provisioned credentials keep working: the `matrix-token-<name>` files and `matrix-account-<name>.json` sidecars the old route wrote are still discovered by hive-matrix-mcp (`accounts::configured` -> `discover_token_accounts`), the `matrix-token*` path unit still re-fires the daemon, and `WriteAgentMatrixToken` stays for the swarm credential worker. Removing that usage waits on moving the existing creds to swarm level. The GITHUB tab is the credentials page's default tab now. Refs #4348
This commit is contained in:
parent
93bbec015f
commit
6a1d85c24f
12 changed files with 35 additions and 972 deletions
|
|
@ -2,9 +2,9 @@
|
|||
(.page-header / .page-back / .page-title) comes from the shared
|
||||
chrome.css imported by common.css; base tab styling lives in
|
||||
@hive/shared/tabs.css (.hive-tab*) same as /logs.html. This file holds
|
||||
the account-list + provision-form styling specific to this surface,
|
||||
carried over from the old /matrix-accounts.html (`.ma-*` classes) plus
|
||||
the tab-strip layout delta + github-tab additions (`.cred-*`). */
|
||||
the account-list + provision-form styling specific to this surface
|
||||
(`.ma-*` classes) plus the tab-strip layout delta + github-tab
|
||||
additions (`.cred-*`). */
|
||||
|
||||
body.cred-shell {
|
||||
margin: 0;
|
||||
|
|
@ -81,29 +81,6 @@ body.cred-shell {
|
|||
border-color: var(--purple);
|
||||
}
|
||||
|
||||
.ma-mode {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 0.45rem 0.75rem 0.6rem;
|
||||
margin: 0.85rem 0;
|
||||
}
|
||||
.ma-mode legend {
|
||||
font-size: 0.8rem;
|
||||
color: var(--muted);
|
||||
padding: 0 0.3rem;
|
||||
}
|
||||
.ma-mode label {
|
||||
margin-right: 1.3rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ma-modefields {
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
|
||||
.ma-list {
|
||||
margin: 0.5rem 0 1.2rem;
|
||||
}
|
||||
.ma-accounts {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
|
@ -122,38 +99,13 @@ body.cred-shell {
|
|||
border-radius: 50%;
|
||||
flex: none;
|
||||
}
|
||||
/* `ok` is the v1 (pre BE-4) token-present green; `live` is the v2 online green.
|
||||
`offline`/`stale` are the amber states (provisioned-not-live / container-down
|
||||
⟹ daemon-down). `absent` = no token. */
|
||||
.ma-dot.ok,
|
||||
.ma-dot.live {
|
||||
.ma-dot.ok {
|
||||
background: var(--green);
|
||||
}
|
||||
.ma-dot.offline,
|
||||
.ma-dot.stale {
|
||||
background: var(--amber);
|
||||
}
|
||||
.ma-dot.absent {
|
||||
background: var(--muted);
|
||||
}
|
||||
/* `live stale-age`: snapshot still says live but the daemon heartbeat stalled
|
||||
(> ~90s). Keep the green hue but dim + desaturate so it reads "was online,
|
||||
now uncertain" — visually distinct from the solid amber container-down
|
||||
`stale`. More-specific (3 classes) so it overrides `.ma-dot.live`. */
|
||||
.ma-dot.live.stale-age {
|
||||
background: var(--green);
|
||||
opacity: 0.4;
|
||||
filter: saturate(0.45);
|
||||
}
|
||||
.ma-name {
|
||||
font-weight: 600;
|
||||
color: var(--fg);
|
||||
}
|
||||
.ma-uid {
|
||||
color: var(--muted);
|
||||
font-size: 0.8rem;
|
||||
margin-left: 0.4em;
|
||||
}
|
||||
.ma-hs {
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
|
|
@ -162,21 +114,9 @@ body.cred-shell {
|
|||
margin-left: auto;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.ma-status.ok,
|
||||
.ma-status.live {
|
||||
.ma-status.ok {
|
||||
color: var(--green);
|
||||
}
|
||||
.ma-status.offline,
|
||||
.ma-status.stale {
|
||||
color: var(--amber);
|
||||
}
|
||||
.ma-status.absent {
|
||||
color: var(--muted);
|
||||
}
|
||||
.ma-status.live.stale-age {
|
||||
color: var(--green);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.ma-result {
|
||||
margin-top: 0.7rem;
|
||||
|
|
@ -189,8 +129,3 @@ body.cred-shell {
|
|||
.ma-result.err {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.ma-list .err {
|
||||
color: var(--red);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
</head>
|
||||
<body class="cred-shell">
|
||||
<!-- Minimal chrome: back link + sub-tab strip, same pattern as
|
||||
logs.html (MATRIX / GITHUB instead of AGENT/INFRA/SYSTEM). Back
|
||||
logs.html (GITHUB / FORGES instead of AGENT/INFRA/SYSTEM). Back
|
||||
link points to the H0M3 hub (served at /). -->
|
||||
<header class="page-header">
|
||||
<a class="page-back" href="/">← home</a>
|
||||
|
|
@ -25,123 +25,14 @@
|
|||
</header>
|
||||
|
||||
<main class="cred-main">
|
||||
<!-- Agent picker: shared across both tabs (one agent selected at a
|
||||
time drives both the matrix account list and the github status). -->
|
||||
<!-- Agent picker: shared across the tabs (one agent selected at a
|
||||
time drives both the github status and the forge list). -->
|
||||
<h3>◇ agent</h3>
|
||||
<label class="ma-field">
|
||||
<span>agent</span>
|
||||
<select id="ma-agent"></select>
|
||||
</label>
|
||||
|
||||
<!-- MATRIX tab: unchanged from the old /matrix-accounts.html, just
|
||||
moved under a tab pane. -->
|
||||
<section
|
||||
class="cred-pane"
|
||||
id="cred-pane-matrix"
|
||||
data-tab-pane="matrix"
|
||||
role="tabpanel"
|
||||
aria-labelledby="cred-tab-matrix"
|
||||
>
|
||||
<p class="meta">
|
||||
provision or log in an <strong>external</strong> matrix account for an
|
||||
agent and store its access token. the token is written to the agent's
|
||||
<code>matrixAccounts.<account>.tokenFile</code> by the host
|
||||
coordinator — it is never displayed back on this page.
|
||||
</p>
|
||||
|
||||
<h3>◇ provisioned accounts</h3>
|
||||
<p class="meta">
|
||||
accounts that have a stored token (provision one below to add it
|
||||
here); a config-declared account that hasn't been provisioned yet
|
||||
won't appear until it has a token. status reflects whether a
|
||||
<em>token is stored</em>, not a live session — a true
|
||||
online/offline indicator is a follow-up that needs the daemon's
|
||||
account registry.
|
||||
</p>
|
||||
<div id="ma-list" class="ma-list">
|
||||
<p class="meta">select an agent to see its matrix accounts.</p>
|
||||
</div>
|
||||
|
||||
<h3>◇ provision / log in</h3>
|
||||
<form id="ma-form" class="ma-form" autocomplete="off">
|
||||
<label class="ma-field">
|
||||
<span>account name</span>
|
||||
<input
|
||||
type="text"
|
||||
name="account"
|
||||
placeholder="e.g. public"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label class="ma-field">
|
||||
<span>homeserver</span>
|
||||
<input
|
||||
type="text"
|
||||
name="homeserver"
|
||||
placeholder="https://matrix.org"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
|
||||
<fieldset class="ma-mode">
|
||||
<legend>login method</legend>
|
||||
<label
|
||||
><input type="radio" name="mode" value="password" checked />
|
||||
password</label
|
||||
>
|
||||
<label
|
||||
><input type="radio" name="mode" value="token" /> existing
|
||||
token</label
|
||||
>
|
||||
</fieldset>
|
||||
|
||||
<div id="ma-pw-fields" class="ma-modefields">
|
||||
<label class="ma-field">
|
||||
<span>user id</span>
|
||||
<input
|
||||
type="text"
|
||||
name="user_id"
|
||||
placeholder="@user:matrix.org"
|
||||
autocomplete="username"
|
||||
/>
|
||||
</label>
|
||||
<label class="ma-field">
|
||||
<span>password</span>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="ma-token-fields" class="ma-modefields" hidden>
|
||||
<label class="ma-field">
|
||||
<span>access token</span>
|
||||
<input type="password" name="token" autocomplete="off" />
|
||||
</label>
|
||||
<label class="ma-field">
|
||||
<span
|
||||
>user id
|
||||
<span class="meta"
|
||||
>(optional — derived via whoami)</span
|
||||
></span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
name="user_id"
|
||||
placeholder="@user:matrix.org"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-spawn">
|
||||
log in & store token
|
||||
</button>
|
||||
<p id="ma-result" class="ma-result" aria-live="polite"></p>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- GITHUB tab: single-account PAT paste. No login flow — the
|
||||
operator pastes an existing PAT for a dedicated bot account.
|
||||
Security-warning banner + a link to generate a PAT. -->
|
||||
|
|
@ -151,7 +42,6 @@
|
|||
data-tab-pane="github"
|
||||
role="tabpanel"
|
||||
aria-labelledby="cred-tab-github"
|
||||
hidden
|
||||
>
|
||||
<hive-warn level="warning">
|
||||
⚠ use a <strong>dedicated bot account</strong>, not a human's —
|
||||
|
|
@ -194,7 +84,7 @@
|
|||
|
||||
<!-- FORGES tab: external Forgejo/Gitea/Codeberg-compatible forges.
|
||||
Entirely dashboard-provisioned, no host-side nix config — same
|
||||
shape as GITHUB plus a base-URL field (like MATRIX's homeserver).
|
||||
shape as GITHUB plus a base-URL field.
|
||||
The operator creates a token on the external forge themselves
|
||||
(however that forge lets them) and pastes label + URL + token
|
||||
below. No remote account minting/revoking — purely local. -->
|
||||
|
|
|
|||
|
|
@ -2,12 +2,9 @@
|
|||
//
|
||||
// Operator surface to provision per-agent credentials without editing the
|
||||
// agent's config repo. Two sub-tabs, sharing one agent picker:
|
||||
// MATRIX — external matrix account login (carried over verbatim from the
|
||||
// old /matrix-accounts.html — see matrix_accounts.rs backend doc
|
||||
// comments for the account/status contract + endpoint shapes).
|
||||
// GITHUB — single-account PAT paste against /api/github-account
|
||||
// (GET -> {present}, POST form-encoded {agent, token} ->
|
||||
// {ok:true}; same error_response shape as matrix-account-login).
|
||||
// {ok:true}; error_response shape on failure).
|
||||
// No account name / homeserver / login mode, and no
|
||||
// live/heartbeat concept for a static PAT — just present/absent.
|
||||
// FORGES — external forge accounts, entirely dashboard-provisioned (no
|
||||
|
|
@ -19,19 +16,13 @@
|
|||
// themselves and pastes it in, same trust model as GITHUB.
|
||||
// Per-tab detail comments live next to their section below.
|
||||
|
||||
import { $, esc, fmtAgeSecs, renderServerWarnings } from "./common.js";
|
||||
import { $, esc, renderServerWarnings } from "./common.js";
|
||||
import { el } from "@hive/shared/dom.js";
|
||||
import "@hive/shared/hive-tab-strip.js";
|
||||
import { themedConfirm, themedToast } from "@hive/shared/modal.js";
|
||||
import { readApiError, problemMessage } from "@hive/shared/api-error.js";
|
||||
|
||||
let agents = [];
|
||||
// agent name → container running (bool), from /api/state. Cross-referenced by
|
||||
// the live dot: a `live: true` account whose container is DOWN is definitively
|
||||
// stale (the daemon can't be up if the container isn't), so we flag it rather
|
||||
// than show a lying green. `undefined` (agent not in the map) = unknown → we
|
||||
// don't flag stale.
|
||||
const containerRunning = new Map();
|
||||
|
||||
async function loadState() {
|
||||
try {
|
||||
|
|
@ -46,8 +37,6 @@ async function loadState() {
|
|||
.map((a) => (typeof a === "string" ? { name: a } : a))
|
||||
.filter((c) => c && c.name);
|
||||
agents = containers.map((c) => c.name).sort();
|
||||
containerRunning.clear();
|
||||
for (const c of containers) containerRunning.set(c.name, !!c.running);
|
||||
} catch {
|
||||
// best-effort: on a failed state read the picker renders empty
|
||||
// ("— no agents —") and the submit guard blocks until an agent is
|
||||
|
|
@ -74,151 +63,6 @@ function renderAgentPicker() {
|
|||
// originally; promoted so swarm-ui shares the same
|
||||
// shape-agnostic reader instead of each side maintaining its own copy.
|
||||
|
||||
// ─── MATRIX tab ────────────────────────────────────────────────────────────
|
||||
// Live status dot — the daemon heartbeats every ~30s (advances as_of_unix),
|
||||
// so a stalled as_of = daemon dead, not just stale snapshot:
|
||||
// green live + running + fresh = online
|
||||
// dim green live but as_of stale > ~90s = heartbeat stopped
|
||||
// amber live + container DOWN = definitively stale
|
||||
// amber token_present + !live = provisioned but offline
|
||||
// grey no token = not provisioned
|
||||
// Container state takes precedence; as_of_unix is tooltipped for freshness.
|
||||
// v1 backend (no `live` field) falls back to token-present rendering.
|
||||
|
||||
async function loadAccounts(agent) {
|
||||
const list = $("ma-list");
|
||||
if (!agent) {
|
||||
list.replaceChildren(
|
||||
el("p", { class: "meta" }, "select an agent to see its matrix accounts."),
|
||||
);
|
||||
return;
|
||||
}
|
||||
list.replaceChildren(el("p", { class: "meta" }, "loading…"));
|
||||
let data;
|
||||
try {
|
||||
const resp = await fetch(
|
||||
"/api/matrix-accounts?agent=" + encodeURIComponent(agent),
|
||||
);
|
||||
if (!resp.ok) throw new Error("HTTP " + resp.status);
|
||||
data = await resp.json();
|
||||
} catch (err) {
|
||||
list.replaceChildren(
|
||||
el(
|
||||
"p",
|
||||
{ class: "err" },
|
||||
"could not load accounts: " +
|
||||
esc(String(err)) +
|
||||
" (the backend endpoint may not be deployed yet).",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const accounts = data.accounts || [];
|
||||
const asOf = typeof data.as_of_unix === "number" ? data.as_of_unix : null;
|
||||
// `false` only when the container is explicitly down; `undefined` (unknown,
|
||||
// e.g. a failed /api/state read) is treated as not-down so we never flag a
|
||||
// false stale.
|
||||
const running = containerRunning.get(agent);
|
||||
// The daemon force-rewrites its snapshot every ~30s, so `as_of_unix` advances
|
||||
// while it's alive — this is a heartbeat, and a stalled value is meaningful.
|
||||
const ageSecs =
|
||||
asOf != null ? Math.max(0, Math.floor(Date.now() / 1000) - asOf) : null;
|
||||
const asOfText =
|
||||
asOf != null
|
||||
? "matrix snapshot · live as of " + fmtAgeSecs(ageSecs) + " ago"
|
||||
: "no daemon snapshot yet";
|
||||
// 3 missed ~30s heartbeats. Past this a `live` snapshot whose container is
|
||||
// NOT down means the daemon stopped publishing (dead/wedged) — dim its dot.
|
||||
const STALE_AGE_SECS = 90;
|
||||
const staleByAge = ageSecs != null && ageSecs > STALE_AGE_SECS;
|
||||
list.replaceChildren();
|
||||
if (!accounts.length) {
|
||||
list.append(
|
||||
el(
|
||||
"p",
|
||||
{ class: "meta" },
|
||||
"no matrix accounts configured for this agent.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const ul = el("ul", { class: "ma-accounts" });
|
||||
for (const acc of accounts) {
|
||||
const present = !!acc.token_present;
|
||||
// 3-state dot. `live` is absent on the v1 backend (pre BE-4); when
|
||||
// undefined, fall back to the v1 token-present rendering so the page
|
||||
// degrades cleanly before the snapshot backend deploys.
|
||||
let cls;
|
||||
let statusText;
|
||||
let dotTitle;
|
||||
if (acc.live === undefined) {
|
||||
cls = present ? "ok" : "absent";
|
||||
statusText = present ? "token stored ✓" : "no token";
|
||||
dotTitle = present ? "token stored" : "no token yet";
|
||||
} else if (acc.live && running === false) {
|
||||
// container down ⟹ daemon down ⟹ a "live" snapshot is stale.
|
||||
cls = "stale";
|
||||
statusText = "container stopped";
|
||||
dotTitle = "container is stopped — live status is stale. " + asOfText;
|
||||
} else if (acc.live && staleByAge) {
|
||||
// Snapshot says live, but the heartbeat (snapshot mtime = as_of) hasn't
|
||||
// advanced in > ~90s while the container is NOT down — the daemon stopped
|
||||
// publishing, so the "live" is no longer trustworthy. Keep the green
|
||||
// family but dim it (distinct from the amber container-down 'stale').
|
||||
cls = "live stale-age";
|
||||
statusText = "online · no heartbeat";
|
||||
dotTitle =
|
||||
"snapshot says live but the daemon heartbeat stalled " +
|
||||
fmtAgeSecs(ageSecs) +
|
||||
" ago (publishes every ~30s) — likely dead or wedged. " +
|
||||
asOfText;
|
||||
} else if (acc.live) {
|
||||
cls = "live";
|
||||
statusText = "online ✓";
|
||||
dotTitle = asOfText;
|
||||
} else if (present) {
|
||||
cls = "offline";
|
||||
statusText = "token stored · offline";
|
||||
dotTitle = "provisioned but not live. " + asOfText;
|
||||
} else {
|
||||
cls = "absent";
|
||||
statusText = "no token";
|
||||
dotTitle = "no token yet";
|
||||
}
|
||||
ul.append(
|
||||
el(
|
||||
"li",
|
||||
{ class: "ma-account" },
|
||||
el("span", { class: "ma-dot " + cls, title: dotTitle }),
|
||||
el("span", { class: "ma-name" }, acc.name || "(unnamed)"),
|
||||
acc.user_id ? el("span", { class: "ma-uid" }, acc.user_id) : null,
|
||||
el("span", { class: "ma-hs" }, acc.homeserver || "—"),
|
||||
el("span", { class: "ma-status " + cls, title: asOfText }, statusText),
|
||||
),
|
||||
);
|
||||
}
|
||||
list.append(ul);
|
||||
}
|
||||
|
||||
// Show only the fields for the selected login method, and DISABLE the
|
||||
// hidden section's inputs so they don't ride along in the FormData (both
|
||||
// sections carry a `user_id` field, so without this the wrong one — or
|
||||
// both — would be submitted).
|
||||
function toggleModeFields() {
|
||||
const mode = document.querySelector('input[name="mode"]:checked');
|
||||
const value = mode ? mode.value : "password";
|
||||
const pw = $("ma-pw-fields");
|
||||
const tok = $("ma-token-fields");
|
||||
pw.hidden = value !== "password";
|
||||
tok.hidden = value !== "token";
|
||||
pw.querySelectorAll("input").forEach((i) => {
|
||||
i.disabled = pw.hidden;
|
||||
});
|
||||
tok.querySelectorAll("input").forEach((i) => {
|
||||
i.disabled = tok.hidden;
|
||||
});
|
||||
}
|
||||
|
||||
function clearSecrets(formEl) {
|
||||
formEl
|
||||
.querySelectorAll('input[type="password"], input[name="token"]')
|
||||
|
|
@ -227,75 +71,6 @@ function clearSecrets(formEl) {
|
|||
});
|
||||
}
|
||||
|
||||
async function submitLogin(e) {
|
||||
e.preventDefault();
|
||||
const formEl = e.target;
|
||||
const out = $("ma-result");
|
||||
out.className = "ma-result";
|
||||
out.textContent = "";
|
||||
|
||||
const agent = $("ma-agent").value;
|
||||
if (!agent) {
|
||||
out.className = "ma-result err";
|
||||
out.textContent = "select an agent first.";
|
||||
return;
|
||||
}
|
||||
|
||||
const fd = new FormData(formEl);
|
||||
fd.set("agent", agent);
|
||||
|
||||
const btn = formEl.querySelector('button[type="submit"]');
|
||||
const orig = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = "logging in…";
|
||||
|
||||
try {
|
||||
const resp = await fetch("/api/matrix-account-login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams(fd),
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
// Success is 200 + JSON { ok, user_id }.
|
||||
let body = {};
|
||||
try {
|
||||
body = await resp.json();
|
||||
} catch {
|
||||
/* tolerate odd 2xx body */
|
||||
}
|
||||
if (body.ok) {
|
||||
out.className = "ma-result ok";
|
||||
out.textContent =
|
||||
"✓ logged in as " +
|
||||
(body.user_id || "(unknown)") +
|
||||
" — token stored.";
|
||||
clearSecrets(formEl);
|
||||
loadAccounts(agent);
|
||||
} else {
|
||||
out.className = "ma-result err";
|
||||
out.textContent = "✗ login failed (unexpected response).";
|
||||
clearSecrets(formEl);
|
||||
}
|
||||
} else {
|
||||
const msg = problemMessage(await readApiError(resp));
|
||||
out.className = "ma-result err";
|
||||
out.textContent =
|
||||
"✗ " + (msg || "login failed (HTTP " + resp.status + ")");
|
||||
clearSecrets(formEl);
|
||||
}
|
||||
} catch (err) {
|
||||
out.className = "ma-result err";
|
||||
out.textContent =
|
||||
"✗ request failed: " +
|
||||
String(err) +
|
||||
" (the backend endpoint may not be deployed yet).";
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = orig;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── GITHUB tab ─────────────────────────────────────────────────────────
|
||||
|
||||
async function loadGithubStatus(agent) {
|
||||
|
|
@ -581,7 +356,6 @@ async function submitForgeAccount(e) {
|
|||
// ─── init ─────────────────────────────────────────────────────────────
|
||||
|
||||
async function onAgentChange(agent) {
|
||||
loadAccounts(agent);
|
||||
loadGithubStatus(agent);
|
||||
loadForgeAccounts(agent);
|
||||
}
|
||||
|
|
@ -592,21 +366,15 @@ async function init() {
|
|||
$("ma-agent").addEventListener("change", (e) =>
|
||||
onAgentChange(e.target.value),
|
||||
);
|
||||
document
|
||||
.querySelectorAll('input[name="mode"]')
|
||||
.forEach((r) => r.addEventListener("change", toggleModeFields));
|
||||
toggleModeFields();
|
||||
$("ma-form").addEventListener("submit", submitLogin);
|
||||
$("gh-form").addEventListener("submit", submitGithub);
|
||||
$("ef-form").addEventListener("submit", submitForgeAccount);
|
||||
|
||||
document.getElementById("cred-tabbar").configure({
|
||||
tabs: [
|
||||
{ id: "matrix", label: "MATRIX" },
|
||||
{ id: "github", label: "GITHUB" },
|
||||
{ id: "forges", label: "FORGES" },
|
||||
],
|
||||
defaultId: "matrix",
|
||||
defaultId: "github",
|
||||
});
|
||||
|
||||
onAgentChange("");
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@
|
|||
<span class="home-tile-label">Credentials</span>
|
||||
</span>
|
||||
<span class="home-tile-desc"
|
||||
>provision per-agent matrix + github accounts</span
|
||||
>provision per-agent github + forge accounts</span
|
||||
>
|
||||
</a>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue