Files
abrown38andClaude Opus 5.5 0e958289de Web UI: administrators make tokens for other users
New token on a user's row under Users, with the Account page's dialog
(now tokenDialog in users.js), offering only roles within that user's
permissions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 23:49:12 +01:00

44 lines
2.6 KiB
JavaScript

// Your account: password and API tokens.
import { h, mount, table, time, input, field, errorBox, toast } from "../ui.js";
import { get, api, state, catalog } from "../api.js";
import { rerender } from "../app.js";
import { revoke, rotateDialog, tokenDialog } from "./users.js";
export async function render(el) {
const me = state.me;
const [tokens, cat] = await Promise.all([get("/me/tokens"), catalog()]);
const cur = input({ type: "password", autocomplete: "current-password" });
const pw = input({ type: "password", autocomplete: "new-password" });
const pw2 = input({ type: "password", autocomplete: "new-password" });
const err = h("div");
const change = async () => {
mount(err);
if (pw.value !== pw2.value) { mount(err, h("div", { class: "msg err" }, "The new passwords don't match.")); return; }
try {
await api("POST", "/me/password", { body: { current: cur.value, password: pw.value } });
toast("Password changed; your other sessions are logged out");
cur.value = pw.value = pw2.value = "";
} catch (e) { mount(err, errorBox(e)); }
};
mount(el,
h("div", { class: "page-head" }, h("div", { class: "grow" }, h("h1", null, me.user.username),
h("div", { class: "muted" }, me.admin ? "Administrator" : "May: " + (me.permissions.join(", ") || "nothing")))),
me.method === "session" ? h("div", { class: "panel" },
h("h2", null, "Password"),
h("div", { class: "form-grid" }, field("Current password", cur), field("New password", pw), field("Repeat", pw2)), err,
h("div", { class: "actions", style: { marginTop: "12px" } }, h("button", { class: "primary", onclick: change }, "Change password"))) : null,
h("div", { class: "panel" },
h("div", { class: "page-head" }, h("div", { class: "grow" }, h("h2", null, "API tokens"),
h("div", { class: "muted" }, "For the hc CLI and scripts: 'hc config set token'.")),
h("button", { onclick: () => tokenDialog(cat) }, "New token")),
table([
{ label: "Name", render: (t) => t.name },
{ label: "May", render: (t) => h("span", { class: "mono small" }, t.grant_strings.join(" ")) },
{ label: "Expires", render: (t) => t.expires_at ? time(t.expires_at) : "never" },
{ label: "Last used", render: (t) => t.last_used_at ? time(t.last_used_at) : "never" },
{ label: "", tdClass: "actions-cell", render: (t) => [
h("button", { class: "small reveal", onclick: () => rotateDialog(t) }, "Rotate"), " ",
h("button", { class: "small reveal", onclick: () => revoke(t) }, "Revoke")] },
], tokens, { empty: "No tokens." })));
}