swarm-ui, hive dashboard, agent page: one profile menu with the signed-in user, avatar and authelia links
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.
This commit is contained in:
parent
10b8422d93
commit
cd41e1d510
19 changed files with 417 additions and 154 deletions
|
|
@ -11,6 +11,7 @@ import { useApplyThemeOverride } from "@hive/shared/theme-apply.js";
|
|||
import { useApplyMotionOverride } from "@hive/shared/motion-apply.js";
|
||||
import { Header } from "./components/Header.js";
|
||||
import { MetaNav } from "./components/MetaNav.js";
|
||||
import { AgentUserMenu } from "./components/AgentUserMenu.js";
|
||||
import { ExpandDetailsSetting } from "./components/ExpandDetailsSetting.js";
|
||||
import { HideDebugSetting } from "@hive/shared/hide-debug-setting.js";
|
||||
import { StatusChips } from "./components/StatusChips.js";
|
||||
|
|
@ -176,6 +177,11 @@ export function Root() {
|
|||
<ExpandDetailsSetting />
|
||||
<HideDebugSetting />
|
||||
</SettingsMenu>
|
||||
{state ? (
|
||||
<AgentUserMenu
|
||||
dashboardBase={resolveDashboardBase(state.dashboard_port)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
// Kept mounted regardless of `openPanel` (open/closed toggles just the
|
||||
|
|
|
|||
44
frontend/packages/agent/src/components/AgentUserMenu.tsx
Normal file
44
frontend/packages/agent/src/components/AgentUserMenu.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// <AgentUserMenu> — the profile menu (`@hive/shared/user-menu.js`) in this
|
||||
// page's header. Only behind the gateway: the page is then on the dashboard's
|
||||
// own origin, where the session cookie is valid and nginx proxies
|
||||
// `/api/whoami` to authelia. Opened directly on the agent's own port there is
|
||||
// no sign-in to show, so the page has no menu.
|
||||
//
|
||||
// Authelia's URL, for the settings and sign-out links, comes from the
|
||||
// dashboard's `/api/state` (`authelia_public_url`), the one place hive-c0re
|
||||
// states it.
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { UserMenu } from "@hive/shared/user-menu.js";
|
||||
import "@hive/shared/user-menu.css";
|
||||
import { isBehindGateway } from "../lib/dashboardBase.js";
|
||||
|
||||
export function AgentUserMenu({ dashboardBase }: { dashboardBase: string }) {
|
||||
const behind = isBehindGateway();
|
||||
const [autheliaUrl, setAutheliaUrl] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!behind) return;
|
||||
let cancelled = false;
|
||||
fetch(`${dashboardBase}api/state`)
|
||||
.then((r) =>
|
||||
r.ok ? r.json() : Promise.reject(new Error(`http ${r.status}`)),
|
||||
)
|
||||
.then((state: { authelia_public_url?: string | null }) => {
|
||||
if (!cancelled) setAutheliaUrl(state.authelia_public_url ?? null);
|
||||
})
|
||||
.catch(() => {
|
||||
/* cosmetic only: the menu then shows the name without the links */
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [behind, dashboardBase]);
|
||||
|
||||
if (!behind) return null;
|
||||
return (
|
||||
<UserMenu
|
||||
whoamiUrl={`${dashboardBase}api/whoami`}
|
||||
autheliaUrl={autheliaUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,8 +4,13 @@
|
|||
// a relative path. Behind the gateway this page lives at
|
||||
// `/agent/<name>/` on the dashboard's own origin (`location.origin`);
|
||||
// accessed directly, it's the same host on `dashboardPort`.
|
||||
/** Whether this page is served through the gateway, on the dashboard's origin. */
|
||||
export function isBehindGateway(): boolean {
|
||||
return location.pathname.startsWith("/agent/");
|
||||
}
|
||||
|
||||
export function resolveDashboardBase(dashboardPort: number): string {
|
||||
return location.pathname.startsWith("/agent/")
|
||||
return isBehindGateway()
|
||||
? location.origin + "/"
|
||||
: `${location.protocol}//${location.hostname}:${dashboardPort}/`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue