fix(provision-monitor): chroot-correct paths in Sidebar / JobsTable / FlowPage (#983 follow-up) (#998)
While the operator monitors an in-flight Sovereign from the mothership
wizard surface (`console.openova.io/sovereign/provision/$deploymentId/...`),
every internal link MUST stay scoped under that prefix. Today, three
places escape the chroot to clean root paths intended for the
Sovereign's adult hostname:
1. Sidebar.tsx (mother-monitor sidebar): FLAT_NAV[*].to and SETTINGS_ITEM.to
were hardcoded to clean roots like '/jobs', '/cloud' — clicking a nav
item bounced the operator out of /provision/<id>/* to /sovereign/jobs
(which is either Sovereign-Console route on contabo's mothership view
= 404, or the Sovereign-on-clean-root on adult view = wrong context).
Restore the canonical /provision/$deploymentId/<page> TanStack template;
the params={{ deploymentId }} prop already feeds the substitution.
2. JobsTable.tsx (job row + parent-chip Links): `to=`/jobs/$jobId`` is
valid on the Sovereign adult surface but escapes the chroot on the
mother monitor view. Add a useJobLinkBuilder hook that returns
/provision/<id>/jobs/<jobId> on Catalyst-Zero hostnames and
/jobs/<jobId> on Sovereign hostnames.
3. FlowPage.tsx (canvas leaf-job click navigate): same chroot escape.
Same mode-aware target construction.
The chroot rule (founder framing): the operator CANNOT distinguish
'I'm monitoring my child being born under /provision/<id>/' from
'I'm at home on the adult Sovereign console' visually — every page,
sidebar, link, and chip must look identical (#983 pixel-byte-byte
contract). This commit closes the navigation half of that contract
on the mother side; PR #983 already covered the data-fetch half.
Closes the bug surfaced live on otech118 mid-provision: clicking Jobs
in the sidebar from /sovereign/provision/571a382deb47e50a/dashboard
sent the operator to /sovereign/jobs (404 / wrong scope), and a row
click sent them to /sovereign/jobs/571a382...:install-valkey instead
of /sovereign/provision/<id>/jobs/<id>:install-valkey.
Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
643f9df9dd
commit
11dd19e519
@ -41,6 +41,7 @@ import {
|
||||
} from 'react'
|
||||
import { Link, useNavigate, useParams, useSearch } from '@tanstack/react-router'
|
||||
import { useWizardStore } from '@/entities/deployment/store'
|
||||
import { DETECTED_MODE } from '@/shared/lib/detectMode'
|
||||
import { PortalShell } from './PortalShell'
|
||||
import { resolveApplications, type ApplicationDescriptor } from './applicationCatalog'
|
||||
import { useDeploymentEvents } from './useDeploymentEvents'
|
||||
@ -443,11 +444,17 @@ export function FlowPage({
|
||||
toggleFold(jobId)
|
||||
return
|
||||
}
|
||||
// Leaf: navigate to its own home.
|
||||
navigate({
|
||||
to: '/jobs/$jobId' as never,
|
||||
params: { deploymentId, jobId } as never,
|
||||
})
|
||||
// Leaf: navigate to its own home. Chroot-aware target: when the
|
||||
// operator is on the mother's monitoring surface (deploymentId
|
||||
// present in URL params), stay scoped under
|
||||
// /provision/<id>/jobs/<jobId>; on the Sovereign's adult
|
||||
// hostname the deploymentId is implicit so the clean root form
|
||||
// /jobs/<jobId> is correct.
|
||||
const target =
|
||||
deploymentId && DETECTED_MODE.mode !== 'sovereign'
|
||||
? `/provision/${deploymentId}/jobs/${jobId}`
|
||||
: `/jobs/${jobId}`
|
||||
navigate({ to: target as never })
|
||||
},
|
||||
[navigate, deploymentId, cancelPendingClick, allJobs, toggleFold],
|
||||
)
|
||||
|
||||
@ -26,8 +26,9 @@
|
||||
*/
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { Link, useParams } from '@tanstack/react-router'
|
||||
import type { Job, JobStatus } from '@/lib/jobs.types'
|
||||
import { DETECTED_MODE } from '@/shared/lib/detectMode'
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────────
|
||||
* Pure helpers (exported for unit tests)
|
||||
@ -345,8 +346,34 @@ interface JobRowProps {
|
||||
parentLabel: string
|
||||
}
|
||||
|
||||
/**
|
||||
* useJobLinkBuilder — returns a function that builds the chroot-correct
|
||||
* Link `to` for a job id.
|
||||
*
|
||||
* On the mother's monitoring surface (Catalyst-Zero hostname,
|
||||
* `/sovereign/provision/$deploymentId/...`) every link MUST stay scoped
|
||||
* under `/provision/$deploymentId/jobs/$jobId` — escaping to clean
|
||||
* `/jobs/$jobId` would route the operator to either the mother's own
|
||||
* Sovereign-Console route (404 here) or the legacy /sovereign/jobs
|
||||
* surface (which renders disjointed data).
|
||||
*
|
||||
* On the Sovereign's adult surface (hostname = `console.<sov-fqdn>`)
|
||||
* the deploymentId is implicit from the hostname, so the link uses the
|
||||
* clean root form `/jobs/$jobId`.
|
||||
*/
|
||||
function useJobLinkBuilder(): (jobId: string) => string {
|
||||
const params = useParams({ strict: false }) as { deploymentId?: string }
|
||||
const isSovereign = DETECTED_MODE.mode === 'sovereign'
|
||||
const depId = params.deploymentId ?? ''
|
||||
return (jobId: string) =>
|
||||
isSovereign || !depId
|
||||
? `/jobs/${jobId}`
|
||||
: `/provision/${depId}/jobs/${jobId}`
|
||||
}
|
||||
|
||||
function JobRow({ job, parentLabel }: JobRowProps) {
|
||||
const started = formatRelative(job.startedAt)
|
||||
const jobLink = useJobLinkBuilder()
|
||||
return (
|
||||
<tr
|
||||
className="jobs-row"
|
||||
@ -355,8 +382,7 @@ function JobRow({ job, parentLabel }: JobRowProps) {
|
||||
>
|
||||
<td className="jobs-cell jobs-cell-name">
|
||||
<Link
|
||||
to={`/jobs/$jobId` as never}
|
||||
params={{ jobId: job.id } as never}
|
||||
to={jobLink(job.id) as never}
|
||||
className="jobs-row-link"
|
||||
data-testid={`jobs-row-link-${job.id}`}
|
||||
>
|
||||
@ -387,8 +413,7 @@ function JobRow({ job, parentLabel }: JobRowProps) {
|
||||
group as its host job (issue #351). */}
|
||||
{job.parentId ? (
|
||||
<Link
|
||||
to={`/jobs/$jobId` as never}
|
||||
params={{ jobId: job.parentId } as never}
|
||||
to={jobLink(job.parentId) as never}
|
||||
className="jobs-chip jobs-chip-parent jobs-chip-link"
|
||||
data-testid={`jobs-cell-parent-${job.id}`}
|
||||
title={parentLabel}
|
||||
|
||||
@ -75,31 +75,31 @@ const FLAT_NAV: FlatNavItem[] = [
|
||||
{
|
||||
id: 'apps',
|
||||
label: 'Apps',
|
||||
to: '/dashboard' as never,
|
||||
to: '/provision/$deploymentId',
|
||||
icon: 'M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z',
|
||||
},
|
||||
{
|
||||
id: 'jobs',
|
||||
label: 'Jobs',
|
||||
to: '/jobs' as never,
|
||||
to: '/provision/$deploymentId/jobs',
|
||||
icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4',
|
||||
},
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
to: '/dashboard' as never,
|
||||
to: '/provision/$deploymentId/dashboard',
|
||||
icon: 'M3 3h7v9H3V3zm11 0h7v5h-7V3zM14 10h7v11h-7V10zM3 14h7v7H3v-7z',
|
||||
},
|
||||
{
|
||||
id: 'cloud',
|
||||
label: 'Cloud',
|
||||
to: '/cloud' as never,
|
||||
to: '/provision/$deploymentId/cloud',
|
||||
icon: CLOUD_ICON,
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
label: 'Users',
|
||||
to: '/users' as never,
|
||||
to: '/provision/$deploymentId/users',
|
||||
// Tabler IconUsers — verbatim path data, viewBox 24x24.
|
||||
icon: 'M9 7a4 4 0 100 8 4 4 0 000-8zM3 21v-2a4 4 0 014-4h4a4 4 0 014 4v2M16 3.13a4 4 0 010 7.75M21 21v-2a4 4 0 00-3-3.87',
|
||||
},
|
||||
@ -108,7 +108,7 @@ const FLAT_NAV: FlatNavItem[] = [
|
||||
const SETTINGS_ITEM: FlatNavItem = {
|
||||
id: 'settings',
|
||||
label: 'Settings',
|
||||
to: '/settings' as never,
|
||||
to: '/provision/$deploymentId/settings',
|
||||
icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z',
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user