From ac65181c368b827b1e6579b16a519ca9b80c2619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Kr=C3=A1sa?= Date: Wed, 12 Aug 2026 13:29:25 +0000 Subject: [PATCH 1/8] feat(hub): redesign server start page with DS-compliant hero and navigation - home.tsx: stopped state renders hero-lite (icon circle, H1, Start my server, helper caption); not_running.tsx redirects there unless the spawn failed or implicit spawn is pending - HubHeader: NavigationMenu primitives, "Tokens" renamed to "Get Token" - Layouts: page body wrapped in DS Content instead of ad-hoc
- Bump @e-infra/design-system to 0.1.9 in hub/ui and landing Co-Authored-By: kimi-k3 --- hub/AGENTS.md | 1 + hub/ui/package.json | 2 +- hub/ui/pnpm-lock.yaml | 215 +++------------------ hub/ui/src/components/HubHeader.tsx | 62 +++--- hub/ui/src/components/Layouts.tsx | 6 +- hub/ui/src/pages/home.tsx | 108 ++++++----- hub/ui/src/pages/not_running.tsx | 96 ++++++---- landing/package.json | 2 +- landing/pnpm-lock.yaml | 288 +++++++++++----------------- 9 files changed, 305 insertions(+), 475 deletions(-) diff --git a/hub/AGENTS.md b/hub/AGENTS.md index 8dbc35d1..22a98c3c 100644 --- a/hub/AGENTS.md +++ b/hub/AGENTS.md @@ -16,6 +16,7 @@ The in-repo JupyterHub image (`mddash-hub`) powering MDDash's hub: stock `quay.i - **`window.appConfig` injection**: each entry HTML has an inline ` diff --git a/hub/ui/src/pages/token.tsx b/hub/ui/src/pages/token.tsx index 84696a7e..ae3d5a32 100644 --- a/hub/ui/src/pages/token.tsx +++ b/hub/ui/src/pages/token.tsx @@ -334,10 +334,7 @@ export function TokenPage() { {formatTime(t.last_activity)} {formatTime(t.created)} - + revokeToken(t.id)} /> ))} From 06869d9e77353f3462b8f67053b490a4e49ff173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Kr=C3=A1sa?= Date: Thu, 13 Aug 2026 06:31:15 +0000 Subject: [PATCH 6/8] fix(hub): fall back to user.admin for nav and extract IconCardHeader admin_access is only injected by the admin page handler in JupyterHub stock context, so the Admin nav link vanished everywhere else; templates now fall back to user.admin from the default context. The icon-bearing card header pattern (six pages, drifting variants) is one IconCardHeader component. Co-Authored-By: kimi-k3 --- hub/ui/admin.html | 2 +- hub/ui/home.html | 2 +- hub/ui/not_running.html | 2 +- hub/ui/spawn.html | 2 +- hub/ui/spawn_pending.html | 2 +- hub/ui/src/components/IconCard.tsx | 36 ++++++++++++++++++++++++++++++ hub/ui/src/pages/ErrorPage.tsx | 18 +++++++-------- hub/ui/src/pages/admin.tsx | 16 +++++-------- hub/ui/src/pages/login.tsx | 22 +++--------------- hub/ui/src/pages/logout.tsx | 11 +++------ hub/ui/src/pages/oauth.tsx | 16 +++++-------- hub/ui/src/pages/token.tsx | 13 +++++------ hub/ui/stop_pending.html | 2 +- hub/ui/token.html | 2 +- 14 files changed, 75 insertions(+), 71 deletions(-) create mode 100644 hub/ui/src/components/IconCard.tsx diff --git a/hub/ui/admin.html b/hub/ui/admin.html index 82847bd8..b0fb0f29 100644 --- a/hub/ui/admin.html +++ b/hub/ui/admin.html @@ -15,7 +15,7 @@ userName: {{ user.name | tojson }}, xsrf: {{ xsrf_token | default("") | tojson }}, logoutUrl: {{ logout_url | default("") | tojson }}, - adminAccess: {{ admin_access | default(false) | tojson }}, + adminAccess: {{ admin_access | default(user.admin) | default(false) | tojson }}, announcement: {{ announcement | default(none) | tojson }}, apiPageLimit: {{ api_page_limit | default(500) | tojson }} }; diff --git a/hub/ui/home.html b/hub/ui/home.html index 1a6aada9..5a8a139d 100644 --- a/hub/ui/home.html +++ b/hub/ui/home.html @@ -15,7 +15,7 @@ userName: {{ user.name | tojson }}, xsrf: {{ xsrf_token | default("") | tojson }}, logoutUrl: {{ logout_url | default("") | tojson }}, - adminAccess: {{ admin_access | default(false) | tojson }}, + adminAccess: {{ admin_access | default(user.admin) | default(false) | tojson }}, {% if announcement_home is string %} announcement: {{ announcement_home | default(none) | tojson }}, {% else %} diff --git a/hub/ui/not_running.html b/hub/ui/not_running.html index 83ba31a3..983f6a52 100644 --- a/hub/ui/not_running.html +++ b/hub/ui/not_running.html @@ -14,7 +14,7 @@ baseUrl: {{ base_url | default("") | tojson }}, userName: {{ user.name | tojson }}, logoutUrl: {{ logout_url | default("") | tojson }}, - adminAccess: {{ admin_access | default(false) | tojson }}, + adminAccess: {{ admin_access | default(user.admin) | default(false) | tojson }}, announcement: {{ announcement | default(none) | tojson }}, failed: {{ failed | default(false) | tojson }}, serverName: {{ server_name | default("") | tojson }}, diff --git a/hub/ui/spawn.html b/hub/ui/spawn.html index d1f61e07..e0a33fb3 100644 --- a/hub/ui/spawn.html +++ b/hub/ui/spawn.html @@ -15,7 +15,7 @@ userName: {{ user.name | tojson }}, xsrf: {{ xsrf_token | default("") | tojson }}, logoutUrl: {{ logout_url | default("") | tojson }}, - adminAccess: {{ admin_access | default(false) | tojson }}, + adminAccess: {{ admin_access | default(user.admin) | default(false) | tojson }}, {% if announcement_spawn is string %} announcement: {{ announcement_spawn | default(none) | tojson }}, {% else %} diff --git a/hub/ui/spawn_pending.html b/hub/ui/spawn_pending.html index 54363e6f..6618cb98 100644 --- a/hub/ui/spawn_pending.html +++ b/hub/ui/spawn_pending.html @@ -15,7 +15,7 @@ userName: {{ user.name | tojson }}, xsrf: {{ xsrf_token | default("") | tojson }}, logoutUrl: {{ logout_url | default("") | tojson }}, - adminAccess: {{ admin_access | default(false) | tojson }}, + adminAccess: {{ admin_access | default(user.admin) | default(false) | tojson }}, announcement: {{ announcement | default(none) | tojson }}, progressUrl: {{ progress_url | default("") | tojson }} }; diff --git a/hub/ui/src/components/IconCard.tsx b/hub/ui/src/components/IconCard.tsx new file mode 100644 index 00000000..e535fa85 --- /dev/null +++ b/hub/ui/src/components/IconCard.tsx @@ -0,0 +1,36 @@ +import type { ReactNode } from "react" + +import { CardDescription, CardHeader, CardTitle } from "@e-infra/design-system" +import type { LucideIcon } from "lucide-react" + +type IconTone = "primary" | "success" | "warning" | "error" + +const TONE_CLASSES: Record = { + primary: "text-primary", + success: "text-success", + warning: "text-warning", + error: "text-error", +} + +/** Card header with a colored icon before the title — used by every hub card page. */ +export function IconCardHeader({ + icon: Icon, + tone = "primary", + title, + description, +}: { + icon: LucideIcon + tone?: IconTone + title: ReactNode + description?: ReactNode +}) { + return ( + + + + {description ? {description} : null} + + ) +} diff --git a/hub/ui/src/pages/ErrorPage.tsx b/hub/ui/src/pages/ErrorPage.tsx index 43368e9a..afbdf774 100644 --- a/hub/ui/src/pages/ErrorPage.tsx +++ b/hub/ui/src/pages/ErrorPage.tsx @@ -1,8 +1,9 @@ import { useEffect } from "react" -import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, P } from "@e-infra/design-system" +import { Button, Card, CardContent, P } from "@e-infra/design-system" import { Home, TriangleAlert } from "lucide-react" +import { IconCardHeader } from "../components/IconCard" import { CenteredLayout } from "../components/Layouts" import { getAppConfig } from "../lib/config" @@ -59,15 +60,12 @@ export function ErrorPage({ notFound = false }: { notFound?: boolean }) { return ( - - - - {friendlyTitle} - - - {cfg.statusCode} {cfg.statusMessage} - - + {cfg.messageHtml ? (

diff --git a/hub/ui/src/pages/admin.tsx b/hub/ui/src/pages/admin.tsx index 37744ecd..fbdd3400 100644 --- a/hub/ui/src/pages/admin.tsx +++ b/hub/ui/src/pages/admin.tsx @@ -14,9 +14,6 @@ import { Button, Card, CardContent, - CardDescription, - CardHeader, - CardTitle, Input, Label, Link, @@ -33,6 +30,7 @@ import { import { Play, Square, Trash2, UserPlus, Users } from "lucide-react" import { toast } from "sonner" +import { IconCardHeader } from "../components/IconCard" import { AuthedLayout, PageBody } from "../components/Layouts" import { HubApi, type HubUserModel } from "../lib/api" import { getAppConfig } from "../lib/config" @@ -100,13 +98,11 @@ export function AdminPage() { > - - - - Users - - Start or stop user servers, grant admin rights, add or remove users. - +

diff --git a/hub/ui/src/pages/login.tsx b/hub/ui/src/pages/login.tsx index edf8c3d7..b32faf87 100644 --- a/hub/ui/src/pages/login.tsx +++ b/hub/ui/src/pages/login.tsx @@ -1,17 +1,7 @@ -import { - Alert, - AlertDescription, - AlertTitle, - Button, - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Small, -} from "@e-infra/design-system" +import { Alert, AlertDescription, AlertTitle, Button, Card, CardContent, Small } from "@e-infra/design-system" import { KeyRound, TriangleAlert } from "lucide-react" +import { IconCardHeader } from "../components/IconCard" import { CenteredLayout } from "../components/Layouts" import { DEV_FALLBACK_BASE_URL, getAppConfig } from "../lib/config" import { mount } from "../lib/mount" @@ -35,13 +25,7 @@ export function LoginPage() { return ( - - - - Sign in - - Access your MDDash workspace - + {!window.isSecureContext ? ( diff --git a/hub/ui/src/pages/logout.tsx b/hub/ui/src/pages/logout.tsx index 90f408f7..09e4aa21 100644 --- a/hub/ui/src/pages/logout.tsx +++ b/hub/ui/src/pages/logout.tsx @@ -1,6 +1,7 @@ -import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, P } from "@e-infra/design-system" +import { Button, Card, CardContent, P } from "@e-infra/design-system" import { CircleCheck } from "lucide-react" +import { IconCardHeader } from "../components/IconCard" import { CenteredLayout } from "../components/Layouts" import { DEV_FALLBACK_BASE_URL, getAppConfig } from "../lib/config" import { mount } from "../lib/mount" @@ -17,13 +18,7 @@ export function LogoutPage() { return ( - - - - Signed out - - Your session has ended - +

Successfully logged out of JupyterHub.