From 30245d7bec74bc178af755e99585b1ee3fe0f34a Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Thu, 18 Jun 2026 15:02:30 +0000 Subject: [PATCH] refactor(ui): adopt PageHeader on ops/admin/media pages (batch B) Replace hand-rolled .page-header title blocks with the shared editorial PageHeader component across 14 pages (Manage, Middleware, Models, NodeBackendLogs, Nodes, P2P, SkillEdit, Skills, Sound, Traces, TTS, Usage, Users, VideoGen). Title/subtitle move into PageHeader; header-own action clusters (Models stats+buttons, Skills search+buttons) move into the actions slot. Tabs, filters, stat cards, ResourceMonitor and page body stay as siblings. Eyebrow is left to auto-derive from the route. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto --- core/http/react-ui/src/pages/Manage.jsx | 6 +- core/http/react-ui/src/pages/Middleware.jsx | 11 ++- core/http/react-ui/src/pages/Models.jsx | 49 ++++++------- .../react-ui/src/pages/NodeBackendLogs.jsx | 19 ++--- core/http/react-ui/src/pages/Nodes.jsx | 19 ++--- core/http/react-ui/src/pages/P2P.jsx | 33 +++++---- core/http/react-ui/src/pages/SkillEdit.jsx | 13 ++-- core/http/react-ui/src/pages/Skills.jsx | 72 +++++++++---------- core/http/react-ui/src/pages/Sound.jsx | 5 +- core/http/react-ui/src/pages/TTS.jsx | 5 +- core/http/react-ui/src/pages/Traces.jsx | 6 +- core/http/react-ui/src/pages/Usage.jsx | 6 +- core/http/react-ui/src/pages/Users.jsx | 6 +- core/http/react-ui/src/pages/VideoGen.jsx | 5 +- 14 files changed, 127 insertions(+), 128 deletions(-) diff --git a/core/http/react-ui/src/pages/Manage.jsx b/core/http/react-ui/src/pages/Manage.jsx index 10d684a6e..feef047d4 100644 --- a/core/http/react-ui/src/pages/Manage.jsx +++ b/core/http/react-ui/src/pages/Manage.jsx @@ -3,6 +3,7 @@ import { useNavigate, useOutletContext, useSearchParams, useLocation } from 'rea import { useTranslation } from 'react-i18next' import { fromState } from '../utils/editorNav' import ResourceMonitor from '../components/ResourceMonitor' +import PageHeader from '../components/PageHeader' import ConfirmDialog from '../components/ConfirmDialog' import NodeDistributionChip from '../components/NodeDistributionChip' import FilterBar from '../components/FilterBar' @@ -448,10 +449,7 @@ export default function Manage() { return (
-
-

{t('manage.title')}

-

{t('manage.subtitle')}

-
+ {/* Resource Monitor */} diff --git a/core/http/react-ui/src/pages/Middleware.jsx b/core/http/react-ui/src/pages/Middleware.jsx index 7be29725b..027967f69 100644 --- a/core/http/react-ui/src/pages/Middleware.jsx +++ b/core/http/react-ui/src/pages/Middleware.jsx @@ -5,6 +5,7 @@ import { fromState } from '../utils/editorNav' import { settingsApi, modelsApi } from '../utils/api' import LoadingSpinner from '../components/LoadingSpinner' import Toggle from '../components/Toggle' +import PageHeader from '../components/PageHeader' // Middleware admin page. Three tabs: // - Filtering: per-model resolved PII state + per-model detector list @@ -129,12 +130,10 @@ export default function Middleware() { return (
-
-

Middleware

-

- Inspect and configure routing-module middleware: PII filtering and intelligent routing. -

-
+ {/* Tab bar */}
diff --git a/core/http/react-ui/src/pages/Models.jsx b/core/http/react-ui/src/pages/Models.jsx index be4afaff1..c41dde2e5 100644 --- a/core/http/react-ui/src/pages/Models.jsx +++ b/core/http/react-ui/src/pages/Models.jsx @@ -8,6 +8,7 @@ import { useDebouncedCallback } from '../hooks/useDebounce' import { useOperations } from '../hooks/useOperations' import { useResources } from '../hooks/useResources' import SearchableSelect from '../components/SearchableSelect' +import PageHeader from '../components/PageHeader' import ConfirmDialog from '../components/ConfirmDialog' import GalleryLoader from '../components/GalleryLoader' import Toggle from '../components/Toggle' @@ -271,32 +272,32 @@ export default function Models() { return (
-
-
-

{t('title')}

-

{t('subtitle')}

-
-
-
-
-
{stats.total}
-
{t('stats.available')}
-
-
- navigate('/app/manage')} style={{ cursor: 'pointer' }}> -
{stats.installed}
-
{t('stats.installed')}
-
+ + + +
- - -
-
+ } + /> {/* Search */}
diff --git a/core/http/react-ui/src/pages/NodeBackendLogs.jsx b/core/http/react-ui/src/pages/NodeBackendLogs.jsx index 58e798233..e1dd76410 100644 --- a/core/http/react-ui/src/pages/NodeBackendLogs.jsx +++ b/core/http/react-ui/src/pages/NodeBackendLogs.jsx @@ -4,6 +4,7 @@ import { nodesApi } from '../utils/api' import { formatTimestamp } from '../utils/format' import { apiUrl } from '../utils/basePath' import LoadingSpinner from '../components/LoadingSpinner' +import PageHeader from '../components/PageHeader' function wsUrl(path) { const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:' @@ -176,9 +177,9 @@ export default function NodeBackendLogs() { return (
-
-
-

+ {baseModelName} {!isMerged && ( @@ -217,13 +218,15 @@ export default function NodeBackendLogs() { merged ยท {replicas.length} replicas )} -

-

+ + } + supporting={ + <> Backend logs from node {nodeName || nodeId} {' '}(back to nodes) -

-
-
+ + } + /> {showReplicaToggle && (
diff --git a/core/http/react-ui/src/pages/Nodes.jsx b/core/http/react-ui/src/pages/Nodes.jsx index 06372f4b1..8fb258fcb 100644 --- a/core/http/react-ui/src/pages/Nodes.jsx +++ b/core/http/react-ui/src/pages/Nodes.jsx @@ -3,6 +3,7 @@ import { useOutletContext, useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { nodesApi } from '../utils/api' import LoadingSpinner from '../components/LoadingSpinner' +import PageHeader from '../components/PageHeader' import ConfirmDialog from '../components/ConfirmDialog' import ActionMenu from '../components/ActionMenu' import SearchableModelSelect from '../components/SearchableModelSelect' @@ -995,15 +996,15 @@ export default function Nodes() { return (
-
-

- - {t('nodes.title')} -

-

- {t('nodes.subtitle')} -

-
+ + + {t('nodes.title')} + + } + supporting={t('nodes.subtitle')} + /> {/* Tabs */}
diff --git a/core/http/react-ui/src/pages/P2P.jsx b/core/http/react-ui/src/pages/P2P.jsx index 7e52d1c77..b67d84110 100644 --- a/core/http/react-ui/src/pages/P2P.jsx +++ b/core/http/react-ui/src/pages/P2P.jsx @@ -3,6 +3,7 @@ import { useOutletContext } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { p2pApi } from '../utils/api' import LoadingSpinner from '../components/LoadingSpinner' +import PageHeader from '../components/PageHeader' import ImageSelector, { useImageSelector, dockerImage, dockerFlags } from '../components/ImageSelector' function NodeCard({ node, label, iconColor, iconBg }) { @@ -295,20 +296,24 @@ export default function P2P() { return (
-
-

- - {t('p2p.title')} -

-

- {t('p2p.subtitle')} - {' '} - - - -

-
+ + + {t('p2p.title')} + + } + supporting={ + <> + {t('p2p.subtitle')} + {' '} + + + + + } + /> {/* Network Token */}
navigate('/app/skills')}> Back to skills -
-

- {isNew ? 'New skill' : `Edit: ${name}`} -

-
+ + {isNew ? 'New skill' : `Edit: ${name}`} + + } + />
diff --git a/core/http/react-ui/src/pages/Skills.jsx b/core/http/react-ui/src/pages/Skills.jsx index 735bcc026..954912cfd 100644 --- a/core/http/react-ui/src/pages/Skills.jsx +++ b/core/http/react-ui/src/pages/Skills.jsx @@ -5,6 +5,7 @@ import { skillsApi } from '../utils/api' import { useAuth } from '../context/AuthContext' import { useUserMap } from '../hooks/useUserMap' import UserGroupSection from '../components/UserGroupSection' +import PageHeader from '../components/PageHeader' import ConfirmDialog from '../components/ConfirmDialog' export default function Skills() { @@ -207,10 +208,7 @@ export default function Skills() { if (unavailable) { return (
-
-

{t('title')}

-

{t('unavailable.subtitle')}

-
+
- - -
-
+ + + +
+ } + /> {showGitRepos && (
diff --git a/core/http/react-ui/src/pages/Sound.jsx b/core/http/react-ui/src/pages/Sound.jsx index 8ec783fc8..bb9555b1f 100644 --- a/core/http/react-ui/src/pages/Sound.jsx +++ b/core/http/react-ui/src/pages/Sound.jsx @@ -1,6 +1,7 @@ import { useState } from 'react' import { useParams, useOutletContext } from 'react-router-dom' import ModelSelector from '../components/ModelSelector' +import PageHeader from '../components/PageHeader' import { CAP_SOUND_GENERATION } from '../utils/capabilities' import LoadingSpinner from '../components/LoadingSpinner' import ErrorWithTraceLink from '../components/ErrorWithTraceLink' @@ -77,9 +78,7 @@ export default function Sound() { return (
-
-

Sound Generation

-
+ Sound Generation} />
diff --git a/core/http/react-ui/src/pages/TTS.jsx b/core/http/react-ui/src/pages/TTS.jsx index fb812d485..99d836845 100644 --- a/core/http/react-ui/src/pages/TTS.jsx +++ b/core/http/react-ui/src/pages/TTS.jsx @@ -2,6 +2,7 @@ import { useState } from 'react' import { useParams, useOutletContext } from 'react-router-dom' import { useTranslation } from 'react-i18next' import ModelSelector from '../components/ModelSelector' +import PageHeader from '../components/PageHeader' import { CAP_TTS } from '../utils/capabilities' import LoadingSpinner from '../components/LoadingSpinner' import ErrorWithTraceLink from '../components/ErrorWithTraceLink' @@ -49,9 +50,7 @@ export default function TTS() { return (
-
-

{t('tts.title')}

-
+ {t('tts.title')}} />
diff --git a/core/http/react-ui/src/pages/Traces.jsx b/core/http/react-ui/src/pages/Traces.jsx index 4d0efe229..db7653bdf 100644 --- a/core/http/react-ui/src/pages/Traces.jsx +++ b/core/http/react-ui/src/pages/Traces.jsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next' import { tracesApi, settingsApi } from '../utils/api' import { formatTimestamp } from '../utils/format' import LoadingSpinner from '../components/LoadingSpinner' +import PageHeader from '../components/PageHeader' import Toggle from '../components/Toggle' import SettingRow from '../components/SettingRow' import WaveformPlayer from '../components/audio/WaveformPlayer' @@ -407,10 +408,7 @@ export default function Traces() { return (
-
-

{t('traces.title')}

-

{t('traces.subtitle')}

-
+