The swarm UI's user menu moves to @hive/shared and takes the whoami URL and authelia's URL as props; the swarm UI keeps a thin wrapper, and the hive dashboard and the per-agent page mount the same component. The dashboard's nginx vhost proxies /api/whoami to authelia (the agent page is served from the same vhost), and hive-c0re's state carries authelia_public_url, set from services.hyperhive.swarm.authelia.url. The agent page shows the menu only behind the gateway.
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
// What <UserMenu> shows of the signed-in user: the name and e-mail authelia
|
|
// reports, the initials avatar built from them, and the authelia pages the
|
|
// menu links. Pure, so the swarm UI's tests can run it.
|
|
|
|
export interface WhoAmI {
|
|
displayName: string | null;
|
|
email: string | null;
|
|
}
|
|
|
|
// The seven chromatic theme colours the avatar is picked from, by name.
|
|
const AVATAR_ACCENTS = [
|
|
"var(--red)",
|
|
"var(--yellow)",
|
|
"var(--green)",
|
|
"var(--cyan)",
|
|
"var(--blue)",
|
|
"var(--purple)",
|
|
"var(--pink)",
|
|
];
|
|
|
|
/** A colour for `seed`: the same name always gets the same one. */
|
|
export function pickAccent(seed: string): string {
|
|
let hash = 0;
|
|
for (let i = 0; i < seed.length; i++)
|
|
hash = (hash * 31 + seed.charCodeAt(i)) | 0;
|
|
return AVATAR_ACCENTS[Math.abs(hash) % AVATAR_ACCENTS.length];
|
|
}
|
|
|
|
/**
|
|
* Lenient parse of authelia's `GET /api/user/info`, which wraps its answer in
|
|
* a `{status, data}` envelope. The menu is cosmetic, so a shape that is not
|
|
* recognised gives no name rather than an error.
|
|
*/
|
|
export function parseWhoAmI(body: unknown): WhoAmI {
|
|
const record = (body ?? {}) as Record<string, unknown>;
|
|
const data = (record.data ?? record) as Record<string, unknown>;
|
|
const displayName =
|
|
typeof data.display_name === "string" ? data.display_name : null;
|
|
const emails = Array.isArray(data.emails) ? data.emails : null;
|
|
const email =
|
|
emails && typeof emails[0] === "string" ? (emails[0] as string) : null;
|
|
return { displayName, email };
|
|
}
|
|
|
|
/** The name to show: the display name, else the e-mail, else none. */
|
|
export function labelOf(who: WhoAmI | null): string | null {
|
|
return who?.displayName || who?.email || null;
|
|
}
|
|
|
|
/** The letter in the avatar, or "?" when there is no name. */
|
|
export function initialOf(label: string | null): string {
|
|
return label ? label.trim().charAt(0).toUpperCase() : "?";
|
|
}
|
|
|
|
/** Authelia's settings and sign-out pages under `autheliaUrl`, if it is known. */
|
|
export function accountLinks(
|
|
autheliaUrl: string | null,
|
|
): { settings: string; logout: string } | null {
|
|
if (!autheliaUrl) return null;
|
|
// The trailing slash comes off so `/settings` appends cleanly.
|
|
const base = autheliaUrl.replace(/\/+$/, "");
|
|
return { settings: `${base}/settings`, logout: `${base}/logout` };
|
|
}
|