From 8862b5599940d3c903ddf02013007e8af91746a3 Mon Sep 17 00:00:00 2001 From: "benjamin.747" Date: Wed, 29 Jul 2026 14:20:10 +0800 Subject: [PATCH 1/3] feat(moon): unify Orion /oc fleet table by environment Merge runner and client rows into one table with This env / Other / All filters, truncate overflowing cells with tooltips, and restore start time plus heartbeat columns. --- .../components/OrionClient/RunnersTable.tsx | 502 ++++++++++++++---- .../components/OrionClient/TruncatedText.tsx | 76 +++ .../OrionClient/domainFromHostname.ts | 42 ++ .../apps/web/components/OrionClient/index.tsx | 1 + moon/apps/web/pages/[org]/oc/index.tsx | 136 ++--- 5 files changed, 554 insertions(+), 203 deletions(-) create mode 100644 moon/apps/web/components/OrionClient/TruncatedText.tsx create mode 100644 moon/apps/web/components/OrionClient/domainFromHostname.ts diff --git a/moon/apps/web/components/OrionClient/RunnersTable.tsx b/moon/apps/web/components/OrionClient/RunnersTable.tsx index a18629326..ea0103851 100644 --- a/moon/apps/web/components/OrionClient/RunnersTable.tsx +++ b/moon/apps/web/components/OrionClient/RunnersTable.tsx @@ -5,8 +5,17 @@ import { ThemeProvider } from '@primer/react' import { DataTable } from '@primer/react/experimental' import { useTheme } from 'next-themes' -import type { RunnerStatusResponse } from '@gitmono/types/generated' -import { Badge, Button, UIText } from '@gitmono/ui' +import { ORION_API_URL } from '@gitmono/config' +import { CoreWorkerStatus, RunnerStatusResponse, TaskPhase } from '@gitmono/types/generated' +import { Button, Select, SelectTrigger, SelectValue, UIText } from '@gitmono/ui' + +import { useGetOrionClientStatusById } from '@/hooks/OrionClient/OrionClientStatusById' + +import { domainFromClientHostname, isLocalEnvironmentDomain, localOrionDomainFromUrl } from './domainFromHostname' +import { TruncatedText } from './TruncatedText' +import { deriveStatus, OrionClient, OrionClientStatus } from './types' + +type EnvFilter = 'local' | 'other' | 'all' function colWidth(parts: number) { return `minmax(0, ${parts}fr)` @@ -22,140 +31,330 @@ function formatUptime(totalSecs: number | null | undefined): string { const secs = Math.max(0, Math.floor(totalSecs)) const h = Math.floor(secs / 3600) const m = Math.floor((secs % 3600) / 60) - const s = secs % 60 if (h > 0) return `${h}h ${m}m` - if (m > 0) return `${m}m ${s}s` - return `${s}s` + if (m > 0) return `${m}m` + return `${secs}s` } -function phaseBadge(phase: string) { - const normalized = phase.toLowerCase() - const color = - normalized === 'running' - ? 'green' - : normalized === 'provisioning' - ? 'blue' - : normalized === 'failed' - ? 'brand' - : 'default' +function formatDateTime(iso: string | null | undefined): string { + if (!iso) return '—' + const d = new Date(iso) - return ( - - {phase || 'unknown'} - - ) + if (Number.isNaN(d.getTime())) return iso + return d.toLocaleString() +} + +function formatRelative(iso: string) { + const d = new Date(iso) + const ts = d.getTime() + + if (Number.isNaN(ts)) return '—' + + const diffSec = Math.max(0, Math.floor((Date.now() - ts) / 1000)) + + if (diffSec < 60) return `${diffSec}s ago` + const diffMin = Math.floor(diffSec / 60) + + if (diffMin < 60) return `${diffMin}m ago` + const diffHour = Math.floor(diffMin / 60) + + if (diffHour < 24) return `${diffHour}h ago` + return `${Math.floor(diffHour / 24)}d ago` +} + +function phaseLabel(phase: string | null | undefined): string { + if (!phase) return '—' + return phase +} + +function phaseClass(phase: string | null | undefined): string { + const p = (phase || '').toLowerCase() + + if (p === 'running') return 'text-green-700 dark:text-green-400' + if (p === 'provisioning') return 'text-blue-700 dark:text-blue-400' + if (p === 'failed') return 'text-red-600 dark:text-red-400' + return 'text-tertiary' +} + +export type FleetRow = { + id: string + domain: string | null + runner: RunnerStatusResponse | null + client: OrionClient | null + isLocalEnv: boolean +} + +function mergeFleetRows( + runners: RunnerStatusResponse[], + clients: OrionClient[], + localOrionDomain: string | null +): FleetRow[] { + const clientByDomain = new Map() + + for (const client of clients) { + const domain = domainFromClientHostname(client.hostname) + + if (!domain) continue + const prev = clientByDomain.get(domain) + + if (!prev || new Date(client.last_heartbeat).getTime() > new Date(prev.last_heartbeat).getTime()) { + clientByDomain.set(domain, client) + } + } + + const usedDomains = new Set() + const rows: FleetRow[] = runners.map((runner) => { + const domain = runner.domain ?? null + + if (domain) usedDomains.add(domain) + return { + id: runner.vm_id, + domain, + runner, + client: domain ? (clientByDomain.get(domain) ?? null) : null, + isLocalEnv: isLocalEnvironmentDomain(domain, localOrionDomain) + } + }) + + for (const client of clients) { + const domain = domainFromClientHostname(client.hostname) + + if (domain && usedDomains.has(domain)) continue + rows.push({ + id: `client:${client.client_id}`, + domain, + runner: null, + client, + isLocalEnv: isLocalEnvironmentDomain(domain, localOrionDomain) + }) + } + + rows.sort((a, b) => { + if (a.isLocalEnv !== b.isLocalEnv) return a.isLocalEnv ? -1 : 1 + const ar = a.runner ? phaseRank(a.runner.phase) : 9 + const br = b.runner ? phaseRank(b.runner.phase) : 9 + + if (ar !== br) return ar - br + return (a.domain ?? a.id).localeCompare(b.domain ?? b.id) + }) + + return rows +} + +function phaseRank(phase: string): number { + switch (phase) { + case 'running': + return 0 + case 'provisioning': + return 1 + case 'failed': + return 2 + default: + return 3 + } } interface RunnersTableProps { runners: RunnerStatusResponse[] + clients: OrionClient[] isLoading?: boolean errorMessage?: string | null - onViewLogs?: (runner: RunnerStatusResponse) => void - onConnectTerminal?: (runner: RunnerStatusResponse) => void + statusFilter: OrionClientStatus | 'all' + onStatusChange: (v: OrionClientStatus | 'all') => void + statusOptions: { value: OrionClientStatus | 'all'; label: string }[] + onViewRunnerLogs?: (runner: RunnerStatusResponse) => void + onConnectRunnerTerminal?: (runner: RunnerStatusResponse) => void + onViewClientLogs?: (client: OrionClient) => void + onConnectClientTerminal?: (client: OrionClient) => void canManage?: boolean } -type Row = RunnerStatusResponse & { id: string } - export function RunnersTable({ runners, + clients, isLoading, errorMessage, - onViewLogs, - onConnectTerminal, + statusFilter, + onStatusChange, + statusOptions, + onViewRunnerLogs, + onConnectRunnerTerminal, + onViewClientLogs, + onConnectClientTerminal, canManage = false }: RunnersTableProps) { const { resolvedTheme } = useTheme() - const rows = React.useMemo(() => runners.map((r) => ({ ...r, id: r.vm_id })), [runners]) - const showActions = Boolean(canManage && (onViewLogs || onConnectTerminal)) + const [envFilter, setEnvFilter] = React.useState('local') + const localOrionDomain = React.useMemo(() => localOrionDomainFromUrl(ORION_API_URL), []) + const rows = React.useMemo( + () => mergeFleetRows(runners, clients, localOrionDomain), + [runners, clients, localOrionDomain] + ) + const localCount = rows.filter((r) => r.isLocalEnv).length + const otherCount = rows.length - localCount + const visibleRows = React.useMemo(() => { + if (envFilter === 'local') return rows.filter((r) => r.isLocalEnv) + if (envFilter === 'other') return rows.filter((r) => !r.isLocalEnv) + return rows + }, [rows, envFilter]) + + const showActions = Boolean( + canManage && (onViewRunnerLogs || onConnectRunnerTerminal || onViewClientLogs || onConnectClientTerminal) + ) const columns = React.useMemo( () => [ { - header: 'VM ID', - field: 'vm_id', - rowHeader: true, - width: colWidth(14), - renderCell: (row: Row) => ( -
- - {row.vm_id} - -
- ) - }, - { - header: 'Domain', + header: 'Host', field: 'domain', - width: colWidth(16), - renderCell: (row: Row) =>
{row.domain || '—'}
- }, - { - header: 'Phase', - field: 'phase', - width: colWidth(12), - renderCell: (row: Row) => phaseBadge(row.phase) + rowHeader: true, + width: colWidth(18), + renderCell: (row: FleetRow) => { + const host = row.domain || row.client?.hostname || '—' + const secondary = row.runner?.vm_id + ? row.runner.vm_ip + ? `${row.runner.vm_id} · ${row.runner.vm_ip}` + : row.runner.vm_id + : row.client?.client_id || null + + return ( +
+
+ + {!row.isLocalEnv ? other : null} +
+ {secondary ? : null} +
+ ) + } }, { - header: 'VM IP', - field: 'vm_ip', + header: 'Status', + field: 'runner', width: colWidth(12), - renderCell: (row: Row) =>
{row.vm_ip || '—'}
+ renderCell: (row: FleetRow) => { + if (row.runner) { + return ( + + {phaseLabel(row.runner.phase)} + + ) + } + if (row.client) return + return + } }, { header: 'Image', - field: 'image_name', + field: 'runner', + id: 'image', width: colWidth(18), - renderCell: (row: Row) => { - const digest = shortDigest(row.image_digest) - const label = row.image_name || digest || '—' + renderCell: (row: FleetRow) => { + if (!row.runner) return + const digest = shortDigest(row.runner.image_digest) + const label = row.runner.image_name || digest || '—' + const full = [row.runner.image_name, row.runner.image_digest, row.runner.image_path] + .filter(Boolean) + .join('\n') + const resources = [ + row.runner.image_cpus != null ? `${row.runner.image_cpus}c` : null, + row.runner.image_memory_mb != null ? `${Math.round(row.runner.image_memory_mb / 1024)}G` : null, + row.runner.image_disk_gb != null ? `${row.runner.image_disk_gb}G disk` : null + ] + .filter(Boolean) + .join(' · ') return ( -
- {label} - {row.image_name && digest ? ( - ({digest}) +
+ + {resources ?
{resources}
: null} +
+ ) + } + }, + { + header: 'Client', + field: 'client', + width: colWidth(12), + renderCell: (row: FleetRow) => { + if (!row.client) return + return ( +
+ + {row.client.orion_version ? ( +
{row.client.orion_version}
) : null}
) } }, { - header: 'Resources', - field: 'image_cpus', + header: 'Start', + field: 'client', + id: 'start-time', width: colWidth(14), - renderCell: (row: Row) => { - const parts = [ - row.image_cpus != null ? `${row.image_cpus} vCPU` : null, - row.image_memory_mb != null ? `${Math.round(row.image_memory_mb / 1024)} GiB` : null, - row.image_disk_gb != null ? `${row.image_disk_gb} GiB disk` : null - ].filter(Boolean) - - return
{parts.length ? parts.join(' / ') : '—'}
+ renderCell: (row: FleetRow) => { + if (!row.client?.start_time) { + if (row.runner?.uptime_secs != null) { + return ( +
+ +
up {formatUptime(row.runner.uptime_secs)}
+
+ ) + } + return + } + return ( +
+
{formatDateTime(row.client.start_time)}
+ {row.runner?.uptime_secs != null ? ( +
up {formatUptime(row.runner.uptime_secs)}
+ ) : null} +
+ ) } }, { - header: 'Uptime', - field: 'uptime_secs', - width: colWidth(10), - renderCell: (row: Row) =>
{formatUptime(row.uptime_secs)}
+ header: 'Heartbeat', + field: 'client', + id: 'heartbeat', + width: colWidth(14), + renderCell: (row: FleetRow) => { + if (!row.client?.last_heartbeat) return + return ( +
+
{formatDateTime(row.client.last_heartbeat)}
+
{formatRelative(row.client.last_heartbeat)}
+
+ ) + } }, ...(showActions ? [ { - header: 'Actions', + header: '', field: 'id', - width: colWidth(14), - renderCell: (row: Row) => ( -
- {onViewLogs ? ( - + ) : row.client && onViewClientLogs ? ( + ) : null} - {onConnectTerminal && row.phase === 'running' ? ( - + ) : row.client && !row.runner && onConnectClientTerminal ? ( + ) : null} @@ -165,36 +364,135 @@ export function RunnersTable({ ] : []) ], - [onConnectTerminal, onViewLogs, showActions] + [onConnectClientTerminal, onConnectRunnerTerminal, onViewClientLogs, onViewRunnerLogs, showActions] ) + if (isLoading && rows.length === 0) { + return ( +
+ Loading… +
+ ) + } + return ( -
-
- - Runner VMs - - - {isLoading ? 'Loading…' : `${runners.length} tracked`} - +
+
+
+ {( + [ + { id: 'local', label: 'This env', count: localCount }, + { id: 'other', label: 'Other', count: otherCount }, + { id: 'all', label: 'All', count: rows.length } + ] as const + ).map((tab) => { + const active = envFilter === tab.id + + return ( + + ) + })} +
+ +
+ {localOrionDomain ? ( + + {localOrionDomain} + + ) : null} + +
+ {errorMessage ? ( {errorMessage} ) : null} + - {rows.length === 0 && !isLoading ? ( -
- - No runner VMs yet. Use Start Runner to provision one — it will stay listed here while the scheduler tracks - it. - -
- ) : ( - - )} +
+ {visibleRows.length === 0 ? ( +
+ + {envFilter === 'local' + ? localOrionDomain + ? `No runners or clients for ${localOrionDomain}` + : 'No runners or clients in this environment' + : envFilter === 'other' + ? 'No runners or clients from other environments' + : 'No runners or clients'} + +
+ ) : ( + + )} +
) } + +function ClientStatusText({ client }: { client: OrionClient }) { + const { data, isLoading, isError } = useGetOrionClientStatusById(client.client_id, undefined, 5 * 60 * 1000) + + if (isLoading) { + return + } + + const status = isError || !data ? deriveStatus(client) : mapApiStatusToUiStatus(data.core_status, data.phase) + + const label = + status === 'downloading' + ? 'Downloading' + : status === 'running' + ? 'Building' + : status === 'offline' + ? 'Offline' + : status.charAt(0).toUpperCase() + status.slice(1) + + return {label} +} + +function mapApiStatusToUiStatus(coreStatus: CoreWorkerStatus, phase: TaskPhase | null | undefined): OrionClientStatus { + if (coreStatus === CoreWorkerStatus.Idle) return 'idle' + if (coreStatus === CoreWorkerStatus.Error) return 'error' + if (coreStatus === CoreWorkerStatus.Lost) return 'offline' + + if (coreStatus === CoreWorkerStatus.Busy) { + if (phase === TaskPhase.DownloadingSource) return 'downloading' + if (phase === TaskPhase.RunningBuild) return 'running' + return 'busy' + } + + return 'idle' +} diff --git a/moon/apps/web/components/OrionClient/TruncatedText.tsx b/moon/apps/web/components/OrionClient/TruncatedText.tsx new file mode 100644 index 000000000..edab013dc --- /dev/null +++ b/moon/apps/web/components/OrionClient/TruncatedText.tsx @@ -0,0 +1,76 @@ +'use client' + +import React from 'react' + +import { Tooltip, UIText } from '@gitmono/ui' + +/** Truncate long cell text; show a tooltip/popup with the full value when clipped (or when popupText is richer). */ +export function TruncatedText({ + text, + popupText, + className, + mono = false +}: { + text: string + /** Optional longer text for the popup (defaults to `text`). */ + popupText?: string + className?: string + mono?: boolean +}) { + const ref = React.useRef(null) + const [truncated, setTruncated] = React.useState(false) + const full = popupText && popupText !== text ? popupText : text + + const measure = React.useCallback(() => { + const el = ref.current + + if (!el) return + setTruncated(el.scrollWidth > el.clientWidth + 1) + }, []) + + React.useLayoutEffect(() => { + measure() + }, [text, measure]) + + React.useEffect(() => { + const el = ref.current + + if (!el || typeof ResizeObserver === 'undefined') return + const ro = new ResizeObserver(() => measure()) + + ro.observe(el) + return () => ro.disconnect() + }, [measure]) + + const content = ( + + {text} + + ) + + const needsPopup = Boolean(text && text !== '—' && (truncated || (popupText && popupText !== text))) + + if (!needsPopup) { + return content + } + + return ( + + {full} + + } + delayDuration={200} + side='top' + align='start' + > + + + ) +} diff --git a/moon/apps/web/components/OrionClient/domainFromHostname.ts b/moon/apps/web/components/OrionClient/domainFromHostname.ts new file mode 100644 index 000000000..f8ec8ed6e --- /dev/null +++ b/moon/apps/web/components/OrionClient/domainFromHostname.ts @@ -0,0 +1,42 @@ +'use client' + +/** Client `hostname` is the WS URL (e.g. wss://orion.example/ws); scheduler keys VMs by that host. */ +export function domainFromClientHostname(hostname: string): string | null { + const raw = hostname.trim() + + if (!raw) return null + + try { + const url = new URL(raw.includes('://') ? raw : `ws://${raw}`) + + return url.hostname || null + } catch { + const host = raw.split('/')[0]?.split(':')[0] + + return host || null + } +} + +/** Orion host for this mega-ui deploy (matches scheduler VM `domain` / Start Runner server_ws). */ +export function localOrionDomainFromUrl(orionApiUrl: string): string | null { + const raw = orionApiUrl.trim() + + if (!raw) return null + + try { + const url = new URL(raw.includes('://') ? raw : `https://${raw}`) + const host = url.hostname?.toLowerCase() + + return host || null + } catch { + return null + } +} + +export function isLocalEnvironmentDomain( + domain: string | null | undefined, + localOrionDomain: string | null | undefined +): boolean { + if (!domain || !localOrionDomain) return false + return domain.trim().toLowerCase() === localOrionDomain.trim().toLowerCase() +} diff --git a/moon/apps/web/components/OrionClient/index.tsx b/moon/apps/web/components/OrionClient/index.tsx index c68de8cfa..9d2ebd9d7 100644 --- a/moon/apps/web/components/OrionClient/index.tsx +++ b/moon/apps/web/components/OrionClient/index.tsx @@ -3,4 +3,5 @@ export * from './StatusBadge' export * from './CapabilitiesBadgeList' export * from './ClientsTable' export * from './RunnersTable' +export * from './domainFromHostname' export * from './VmTerminal' diff --git a/moon/apps/web/pages/[org]/oc/index.tsx b/moon/apps/web/pages/[org]/oc/index.tsx index f8f735d1b..1227a6cd9 100644 --- a/moon/apps/web/pages/[org]/oc/index.tsx +++ b/moon/apps/web/pages/[org]/oc/index.tsx @@ -15,7 +15,13 @@ import { Button, UIText } from '@gitmono/ui' import { RefreshIcon } from '@gitmono/ui/Icons' import { AppLayout } from '@/components/Layout/AppLayout' -import { ClientsTable, OrionClient, OrionClientStatus, RunnersTable, VmTerminal } from '@/components/OrionClient' +import { + domainFromClientHostname, + OrionClient, + OrionClientStatus, + RunnersTable, + VmTerminal +} from '@/components/OrionClient' import AuthAppProviders from '@/components/Providers/AuthAppProviders' import { useAdminCheck } from '@/hooks/admin/useAdminCheck' import { usePostOrionClientsInfo } from '@/hooks/OrionClient/OrionClientsInfo' @@ -25,23 +31,6 @@ import { usePostStartRunner } from '@/hooks/OrionClient/usePostStartRunner' import { useRunnerLogsSSE } from '@/hooks/OrionClient/useRunnerLogsSSE' import { PageWithLayout } from '@/utils/types' -/** Client `hostname` is the WS URL (e.g. wss://orion.example/ws); scheduler keys VMs by that host. */ -function domainFromClientHostname(hostname: string): string | null { - const raw = hostname.trim() - - if (!raw) return null - - try { - const url = new URL(raw.includes('://') ? raw : `ws://${raw}`) - - return url.hostname || null - } catch { - const host = raw.split('/')[0]?.split(':')[0] - - return host || null - } -} - function formatUptime(totalSecs: number): string { const secs = Math.max(0, Math.floor(totalSecs)) const h = Math.floor(secs / 3600) @@ -81,8 +70,6 @@ type TerminalPanelSource = 'runner' | 'client' const OrionClientPage: PageWithLayout = () => { const { resolvedTheme } = useTheme() - const [hostnameInput, setHostnameInput] = React.useState('') - const [debouncedHostname, setDebouncedHostname] = React.useState('') const [statusFilter, setStatusFilter] = React.useState('all') const [currentPage, setCurrentPage] = React.useState(1) /** Stream key for scheduler logs: VM id (after Start Runner) or domain host (from client list). */ @@ -103,7 +90,7 @@ const OrionClientPage: PageWithLayout = () => { const logsFollowRef = React.useRef(true) const runnerLogsRef = React.useRef('') - const perPage = 8 + const perPage = 50 const showingLogs = Boolean(activeLogKey) const showingTerminal = Boolean(activeTerminalKey) const showingOverlay = showingLogs || showingTerminal @@ -156,22 +143,9 @@ const OrionClientPage: PageWithLayout = () => { return () => window.cancelAnimationFrame(id) }, [runnerLogs]) - React.useEffect(() => { - const handle = setTimeout(() => { - setDebouncedHostname(hostnameInput) - }, 500) - - return () => clearTimeout(handle) - }, [hostnameInput]) - const requestPayload = React.useMemo(() => { - const text = debouncedHostname.trim() const additional: PageParamsOrionClientQuery['additional'] = {} - if (text !== '') { - additional.hostname = text - } - if (statusFilter === 'idle') { additional.status = CoreWorkerStatus.Idle } else if (statusFilter === 'error') { @@ -192,7 +166,7 @@ const OrionClientPage: PageWithLayout = () => { pagination: { page: currentPage, per_page: perPage }, additional } - }, [currentPage, debouncedHostname, perPage, statusFilter]) + }, [currentPage, perPage, statusFilter]) const handleRefresh = React.useCallback(() => { if (showingOverlay) return @@ -393,7 +367,7 @@ const OrionClientPage: PageWithLayout = () => { React.useEffect(() => { setCurrentPage(1) - }, [hostnameInput, statusFilter]) + }, [statusFilter]) React.useEffect(() => { setCurrentPage((p) => Math.min(Math.max(1, p), pageCount)) @@ -411,6 +385,19 @@ const OrionClientPage: PageWithLayout = () => { })) }, [clientsPage]) + const statusOptions = React.useMemo( + () => [ + { value: 'all' as const, label: 'All clients' }, + { value: 'idle' as const, label: 'Idle' }, + { value: 'busy' as const, label: 'Busy' }, + { value: 'downloading' as const, label: 'Downloading' }, + { value: 'running' as const, label: 'Building' }, + { value: 'error' as const, label: 'Error' }, + { value: 'offline' as const, label: 'Offline' } + ], + [] + ) + return ( <> @@ -423,12 +410,7 @@ const OrionClientPage: PageWithLayout = () => {
-

Orion Clients

- {!showingOverlay ? ( - - Total clients {total} - - ) : null} +

Orion

{isAdmin ? ( @@ -446,7 +428,7 @@ const OrionClientPage: PageWithLayout = () => { iconOnly={} accessibilityLabel='Refresh' onClick={handleRefresh} - disabled={isPending} + disabled={isPending || (isAdmin && isLoadingRunners)} tooltip='Refresh' /> ) : null} @@ -700,69 +682,21 @@ const OrionClientPage: PageWithLayout = () => { {!showingOverlay ? ( <> - {isAdmin ? ( - - ) : null} - -
-
- - - -
- setHostnameInput(e.target.value)} - placeholder='Search by Hostname' - className='w-full flex-1 border-none bg-transparent text-sm text-gray-700 ring-0 outline-hidden placeholder:text-gray-400 focus:ring-0 focus:outline-hidden dark:text-gray-100 dark:placeholder:text-gray-500' - /> -
- - setStatusFilter(value)} - canViewLogs={isAdmin} - onViewLogs={handleViewClientLogs} - canConnectTerminal={isAdmin} - onConnectTerminal={handleConnectTerminal} - statusOptions={[ - { value: 'all', label: 'All statuses' }, - { value: 'idle', label: 'Idle' }, - { value: 'busy', label: 'Busy' }, - { value: 'downloading', label: '\u00A0\u00A0Downloading source' }, - { value: 'running', label: '\u00A0\u00A0Running build' }, - { value: 'error', label: 'Error' }, - { value: 'offline', label: 'Lost / Offline' } - ]} + statusOptions={statusOptions} + canManage={isAdmin} + onViewRunnerLogs={handleViewRunnerLogs} + onConnectRunnerTerminal={handleConnectRunnerTerminal} + onViewClientLogs={handleViewClientLogs} + onConnectClientTerminal={handleConnectTerminal} /> - {error ? ( - - Failed to load Orion clients: {error.message} - - ) : null} - {pageCount > 1 ? (
From bd92f1b18fbde7bebaa6f8363fa6b83435cd6e72 Mon Sep 17 00:00:00 2001 From: "benjamin.747" Date: Wed, 29 Jul 2026 07:04:43 +0000 Subject: [PATCH 2/3] move qlean test --- Cargo.lock | 38 +++++++++++++------ mono/Cargo.toml | 3 -- orion-scheduler/Cargo.toml | 6 +-- .../tests/buck_service_tests.rs | 2 +- .../tests/campsite_api_store_tests.rs | 2 +- .../tests/cl_merge_integration.rs | 4 +- {mono => orion-scheduler}/tests/common/mod.rs | 0 .../tests/http_server_session_tests.rs | 2 +- .../tests/login_user_extractor_tests.rs | 2 +- .../tests/qlean_integration.rs | 0 .../tests/session_management_tests.rs | 2 +- 11 files changed, 37 insertions(+), 24 deletions(-) rename {mono => orion-scheduler}/tests/buck_service_tests.rs (99%) rename {mono => orion-scheduler}/tests/campsite_api_store_tests.rs (99%) rename {mono => orion-scheduler}/tests/cl_merge_integration.rs (99%) rename {mono => orion-scheduler}/tests/common/mod.rs (100%) rename {mono => orion-scheduler}/tests/http_server_session_tests.rs (98%) rename {mono => orion-scheduler}/tests/login_user_extractor_tests.rs (99%) rename {mono => orion-scheduler}/tests/qlean_integration.rs (100%) rename {mono => orion-scheduler}/tests/session_management_tests.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index fda1c2b2d..f4707dab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1442,7 +1442,7 @@ dependencies = [ "chrono", "common", "futures", - "git-internal 0.8.4", + "git-internal 0.8.5", "hex", "io-orbit", "jupiter", @@ -1671,7 +1671,7 @@ dependencies = [ "config", "directories", "envsubst", - "git-internal 0.8.4", + "git-internal 0.8.5", "idgenerator", "pgp", "redis", @@ -3333,7 +3333,7 @@ dependencies = [ "memchr", "natord", "num_cpus", - "path-absolutize", + "path-absolutize 3.1.1", "rayon", "sea-orm 1.1.20", "serde", @@ -3353,9 +3353,9 @@ dependencies = [ [[package]] name = "git-internal" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8af3835c86ce290f8e29c89aec05c044301fc85ac7532dcc86416e193a33630" +checksum = "12cc02fd83b0fa7dff557b1ed4a6095b027083af2f6d84244fb6813da7caaaae" dependencies = [ "ahash 0.8.12", "async-trait", @@ -3378,7 +3378,7 @@ dependencies = [ "memchr", "natord", "num_cpus", - "path-absolutize", + "path-absolutize 4.0.1", "rayon", "ring", "rkyv 0.8.17", @@ -4346,7 +4346,7 @@ dependencies = [ "chrono", "common", "futures", - "git-internal 0.8.4", + "git-internal 0.8.5", "hex", "hmac 0.13.0", "idgenerator", @@ -5085,7 +5085,7 @@ dependencies = [ "common", "ctrlc", "futures", - "git-internal 0.8.4", + "git-internal 0.8.5", "http", "jemallocator", "jupiter", @@ -5096,7 +5096,6 @@ dependencies = [ "orion-client", "orion-scheduler-client", "percent-encoding", - "qlean", "rand 0.10.2", "regex", "reqwest 0.13.4", @@ -5650,6 +5649,7 @@ dependencies = [ "anyhow", "async-stream", "axum", + "common", "futures-util", "qlean", "serde", @@ -5981,7 +5981,16 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5" dependencies = [ - "path-dedot", + "path-dedot 3.1.1", +] + +[[package]] +name = "path-absolutize" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f808742975794703469f67a28dd14b1d1009a1743c18b0353b4b951dbb0068ad" +dependencies = [ + "path-dedot 4.0.1", ] [[package]] @@ -5993,6 +6002,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "path-dedot" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03351d0f1c066c114015408dc6a3e101f080fc03ef9d8d799aa58ec760cac5a6" + [[package]] name = "pathdiff" version = "0.2.3" @@ -6649,7 +6664,8 @@ dependencies = [ [[package]] name = "qlean" version = "0.3.1" -source = "git+https://github.com/benjamin-747/qlean.git?rev=c3bf91e0f4020b580fd5bfe5d3d7d22bc3754651#c3bf91e0f4020b580fd5bfe5d3d7d22bc3754651" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a77496c2bb51caa26294b6f836c61f5b548e9a6f3eb554b891d1e95ede7b8ef" dependencies = [ "anyhow", "console", diff --git a/mono/Cargo.toml b/mono/Cargo.toml index e95a14a78..c0731a209 100644 --- a/mono/Cargo.toml +++ b/mono/Cargo.toml @@ -84,6 +84,3 @@ mimalloc = { workspace = true } [dev-dependencies] tempfile = { workspace = true } jupiter-migrate = { workspace = true } - -[target.'cfg(target_os = "linux")'.dev-dependencies] -qlean = { git = "https://github.com/benjamin-747/qlean.git", rev = "c3bf91e0f4020b580fd5bfe5d3d7d22bc3754651" } diff --git a/orion-scheduler/Cargo.toml b/orion-scheduler/Cargo.toml index febc3d5ce..c9189f1eb 100644 --- a/orion-scheduler/Cargo.toml +++ b/orion-scheduler/Cargo.toml @@ -21,9 +21,9 @@ async-stream = { workspace = true } url = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] -# Git dep (not path): Docker/CI workspaces cannot see a sibling ../../qlean checkout. -# Pin the InteractiveShell PTY API (0.3.1); crates.io is still at 0.3.0. -qlean = { git = "https://github.com/benjamin-747/qlean.git", rev = "c3bf91e0f4020b580fd5bfe5d3d7d22bc3754651" } +qlean = { version = "0.3.1" } [dev-dependencies] anyhow = { workspace = true } +common = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/mono/tests/buck_service_tests.rs b/orion-scheduler/tests/buck_service_tests.rs similarity index 99% rename from mono/tests/buck_service_tests.rs rename to orion-scheduler/tests/buck_service_tests.rs index 71671288f..38305e48a 100644 --- a/mono/tests/buck_service_tests.rs +++ b/orion-scheduler/tests/buck_service_tests.rs @@ -20,7 +20,7 @@ //! ## Running the Test //! //! ```bash -//! cargo test -p mono --test buck_service_tests -- --ignored --nocapture +//! cargo test -p orion-scheduler --test buck_service_tests -- --ignored --nocapture //! ``` //! //! ## Test Design diff --git a/mono/tests/campsite_api_store_tests.rs b/orion-scheduler/tests/campsite_api_store_tests.rs similarity index 99% rename from mono/tests/campsite_api_store_tests.rs rename to orion-scheduler/tests/campsite_api_store_tests.rs index f49356620..6e588c5a1 100644 --- a/mono/tests/campsite_api_store_tests.rs +++ b/orion-scheduler/tests/campsite_api_store_tests.rs @@ -20,7 +20,7 @@ //! ## Running the Test //! //! ```bash -//! cargo test -p mono --test campsite_api_store_tests -- --ignored --nocapture +//! cargo test -p orion-scheduler --test campsite_api_store_tests -- --ignored --nocapture //! ``` //! //! ## Test Design diff --git a/mono/tests/cl_merge_integration.rs b/orion-scheduler/tests/cl_merge_integration.rs similarity index 99% rename from mono/tests/cl_merge_integration.rs rename to orion-scheduler/tests/cl_merge_integration.rs index 4b36269a9..1aa1eeee7 100644 --- a/mono/tests/cl_merge_integration.rs +++ b/orion-scheduler/tests/cl_merge_integration.rs @@ -22,11 +22,11 @@ //! ```bash //! # Run test (note the --ignored flag) //! # No need to build mono - binary is extracted from ECR image -//! cargo test -p mono --test cl_merge_integration -- --ignored --nocapture +//! cargo test -p orion-scheduler --test cl_merge_integration -- --ignored --nocapture //! //! # Override the default ECR image (optional) //! MEGA_ECR_IMAGE=public.ecr.aws/m8q5m4u3/mega/mono-engine:latest-amd64 \ -//! cargo test -p mono --test cl_merge_integration -- --ignored --nocapture +//! cargo test -p orion-scheduler --test cl_merge_integration -- --ignored --nocapture //! ``` //! //! ## Test Design diff --git a/mono/tests/common/mod.rs b/orion-scheduler/tests/common/mod.rs similarity index 100% rename from mono/tests/common/mod.rs rename to orion-scheduler/tests/common/mod.rs diff --git a/mono/tests/http_server_session_tests.rs b/orion-scheduler/tests/http_server_session_tests.rs similarity index 98% rename from mono/tests/http_server_session_tests.rs rename to orion-scheduler/tests/http_server_session_tests.rs index 214aaceea..2de87a965 100644 --- a/mono/tests/http_server_session_tests.rs +++ b/orion-scheduler/tests/http_server_session_tests.rs @@ -21,7 +21,7 @@ //! //! ```bash //! # Run test (note the --ignored flag) -//! cargo test -p mono --test http_server_session_tests -- --ignored --nocapture +//! cargo test -p orion-scheduler --test http_server_session_tests -- --ignored --nocapture //! ``` //! //! ## Test Design diff --git a/mono/tests/login_user_extractor_tests.rs b/orion-scheduler/tests/login_user_extractor_tests.rs similarity index 99% rename from mono/tests/login_user_extractor_tests.rs rename to orion-scheduler/tests/login_user_extractor_tests.rs index 9e1872756..0f0647fb9 100644 --- a/mono/tests/login_user_extractor_tests.rs +++ b/orion-scheduler/tests/login_user_extractor_tests.rs @@ -21,7 +21,7 @@ //! //! ```bash //! # Run test (note the --ignored flag) -//! cargo test -p mono --test login_user_extractor_tests -- --ignored --nocapture +//! cargo test -p orion-scheduler --test login_user_extractor_tests -- --ignored --nocapture //! ``` //! //! ## Test Design diff --git a/mono/tests/qlean_integration.rs b/orion-scheduler/tests/qlean_integration.rs similarity index 100% rename from mono/tests/qlean_integration.rs rename to orion-scheduler/tests/qlean_integration.rs diff --git a/mono/tests/session_management_tests.rs b/orion-scheduler/tests/session_management_tests.rs similarity index 98% rename from mono/tests/session_management_tests.rs rename to orion-scheduler/tests/session_management_tests.rs index d5c9eacb9..8755c3ed8 100644 --- a/mono/tests/session_management_tests.rs +++ b/orion-scheduler/tests/session_management_tests.rs @@ -21,7 +21,7 @@ //! //! ```bash //! # Run test (note the --ignored flag) -//! cargo test -p mono --test session_management_tests -- --ignored --nocapture +//! cargo test -p orion-scheduler --test session_management_tests -- --ignored --nocapture //! ``` //! //! ## Test Design From 2598433746e64b1a89e5d69e9d2882b5bd6fd3a1 Mon Sep 17 00:00:00 2001 From: "benjamin.747" Date: Wed, 29 Jul 2026 15:11:42 +0800 Subject: [PATCH 3/3] bump git-internal to 0.8.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9369be665..13ed2a553 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ orion = { path = "orion" } orion-client = { path = "clients/orion-client" } orion-scheduler-client = { path = "clients/orion-scheduler-client" } -git-internal = "0.8.4" +git-internal = "0.8.5" libvault-core = "0.1.0" #====