An operator links an agent's GitHub personal access token in the swarm UI
(LinkGithubAccountForm, "link github account" on /agents). swarm-controller's
PUT /api/hives/{hive}/agents/{agent}/github-account stores it at
swarm/agents/<agent>/github-token (swarm_secret_client::github), a flat leaf
under the agent's prefix that the agent's existing read grant already covers:
no policy change, and no list grant, since there is one token per agent.
In the agent, hive-agent-github-token (oneshot + 2-minute timer, as the agent
user, under its own store certificate, ordered before hive-github-notify)
reads that path and writes <state>/github-token, 0600 and agent-owned, the
file the gh wrapper, git credential helper and hive-github-notify already
read. It replaces the file by rename only when the bytes changed and never
deletes it: a hive-written github-token stays until a token is linked in the
swarm UI. It is installed only with a store address and
services.hyperhive.agent.github.enable.
Removed: the dashboard's CR3D3NTIALS page (credentials.html/js/css, its
build entries and H0M3 tile; GITHUB was its only tab), hive-c0re's
dashboard/matrix_accounts.rs with GET/POST /api/github-account,
priv_client::write_agent_github_token, the host socket's
SetAgentGithubToken and `hivectl github set-token`, and hive-priv's
WriteAgentGithubToken with write_agent_state_file, its only caller gone.
Docs: integrations/github.md and swarm/ui.md describe the swarm path,
swarm/credentials.md gains the store-path row, and the hive UI docs,
hivectl docs and security.md's hive-priv table drop the removed pieces.
Closes #4347
131 lines
4.9 KiB
JavaScript
131 lines
4.9 KiB
JavaScript
// esbuild build for @hive/dashboard: one JS+CSS bundle per page (H0M3 at
|
|
// `/`, dashboard/flow/logs/core/builds at their own
|
|
// `.html`), each named after its own entry point below — see the
|
|
// `entryPoints`/`for` lists for the exact map, not repeated here to
|
|
// avoid this comment drifting out of sync with the real build steps.
|
|
//
|
|
// Each JS entry bundles `./common.js` (DOM helpers, Panel singleton,
|
|
// NOTIF, path linkification) independently — esbuild inlines the shared
|
|
// module into each bundle rather than emitting a shared chunk (no
|
|
// `splitting: true`). The Rust binary mounts `dist/` as a
|
|
// `tower_http::ServeDir` fallback (`append_index_html_on_directories`
|
|
// serves index.html at /); the dashboard SPA lives at /dashboard.html
|
|
// (a plain file, not a /dashboard/ prefix) so it never shadows the
|
|
// exact-match /dashboard/stream + /dashboard/history SSE routes
|
|
// registered before the ServeDir fallback.
|
|
|
|
import { build } from "esbuild";
|
|
import { mkdirSync, copyFileSync, rmSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const src = (p) => resolve(here, "src", p);
|
|
const dist = (p) => resolve(here, "dist", p);
|
|
const staticDir = (p) => resolve(here, "dist", "static", p);
|
|
|
|
rmSync(dist(""), { recursive: true, force: true });
|
|
mkdirSync(staticDir(""), { recursive: true });
|
|
|
|
// Bundle the JS entries. ES-module output, browser target, no minify
|
|
// (line-aligned source aids debugging; minification belongs in a later
|
|
// follow-up once asset sizes warrant it). esbuild writes each entry
|
|
// to `static/<name>.js` based on the entryPoint basename.
|
|
await build({
|
|
entryPoints: [
|
|
src("tabs.js"),
|
|
src("flow.js"),
|
|
src("logs.js"),
|
|
src("home.js"),
|
|
src("stats.js"),
|
|
src("core.js"),
|
|
src("builds.js"),
|
|
],
|
|
outdir: staticDir(""),
|
|
bundle: true,
|
|
format: "esm",
|
|
platform: "browser",
|
|
target: ["es2022"],
|
|
sourcemap: true,
|
|
logLevel: "info",
|
|
// `@hive/shared/modal.js` and `hive-btn.js` import their shadow-DOM
|
|
// component CSS (hive-dialog.css, hive-toast.css, hive-btn.css) as raw
|
|
// text via a plain `import css from './foo.css'` — the `text` loader turns
|
|
// that into a string constant at bundle time instead of erroring on an
|
|
// unrecognised extension. None of these JS entries import a `.css`
|
|
// file any other way, so this doesn't collide with the separate
|
|
// page-stylesheet bundling below (`loader: { '.css': 'css' }`), which
|
|
// runs as its own esbuild invocation over different entry points.
|
|
loader: { ".css": "text" },
|
|
// `@hive/shared/jobq-graph.js` resolves to a real `.jsx` file
|
|
// (`JobqGraph.jsx`), pulled in transitively by `builds.js` — esbuild
|
|
// already picks the `jsx` loader for `.jsx` by extension, this just
|
|
// sets the transform mode to match swarm-ui's (which also authors
|
|
// this file). No other entry here uses JSX today; this doesn't turn
|
|
// any plain `.js` file into one, `.js` still parses as plain JS.
|
|
jsx: "automatic",
|
|
jsxImportSource: "preact",
|
|
});
|
|
|
|
// Stream-worker entry. Lives in a separate bundle: SharedWorker
|
|
// scripts run in a different global (`self` is the worker scope, no
|
|
// `window`) so they can't be inlined into tabs.js / flow.js. Output is
|
|
// at `static/stream-worker.js`; common.js's `openStream` references
|
|
// `/static/stream-worker.js` as the SharedWorker URL. `format: 'iife'`
|
|
// matches the classic-script load (`new SharedWorker(url, name)` with
|
|
// no `{ type: 'module' }`); Firefox's module SharedWorker support is
|
|
// patchy, so keeping the worker as a classic script + IIFE bundle is
|
|
// the compatible default. argus's own review nit here: if a future
|
|
// contributor adds an `import` to this bundle, the IIFE format will
|
|
// surface it as a build error rather than silently shipping broken code.
|
|
await build({
|
|
entryPoints: [src("stream-worker.js")],
|
|
outdir: staticDir(""),
|
|
bundle: true,
|
|
format: "iife",
|
|
platform: "browser",
|
|
target: ["es2022"],
|
|
sourcemap: true,
|
|
logLevel: "info",
|
|
});
|
|
|
|
// Bundle CSS — one entry per page. esbuild resolves @import including
|
|
// the package re-exports from @hive/shared. Each page loads colors.css
|
|
// (the standalone base16 palette — the theme swap contract, its own file
|
|
// so a swap replaces only it) + theme.css (the semantic derivation
|
|
// layer) + common.css (shared typography, badges, buttons, inbox, side
|
|
// panel) plus its own page-specific bundle.
|
|
for (const entry of [
|
|
"colors.css",
|
|
"theme.css",
|
|
"common.css",
|
|
"dashboard.css",
|
|
"flow.css",
|
|
"logs.css",
|
|
"home.css",
|
|
"stats.css",
|
|
"core.css",
|
|
"builds.css",
|
|
]) {
|
|
await build({
|
|
entryPoints: [src(entry)],
|
|
outfile: staticDir(entry),
|
|
bundle: true,
|
|
loader: { ".css": "css" },
|
|
logLevel: "info",
|
|
});
|
|
}
|
|
|
|
for (const html of [
|
|
"index.html",
|
|
"dashboard.html",
|
|
"flow.html",
|
|
"logs.html",
|
|
"stats.html",
|
|
"core.html",
|
|
"builds.html",
|
|
]) {
|
|
copyFileSync(src(html), dist(html));
|
|
}
|
|
|
|
console.log("dashboard build ok →", dist(""));
|