feat(control-ui): modernize UX with premium Ultrabase design system
Some checks failed
ci / ui (push) Failing after 31s
images / build-and-push (push) Failing after 19s
ci / rust (push) Failing after 2m38s

This commit is contained in:
2026-03-30 21:57:33 +03:00
parent 020ebad570
commit beae85d2d2
4 changed files with 701 additions and 256 deletions

View File

@@ -1,28 +1,61 @@
import { useMemo, useState } from 'react'
import React, { useMemo, useState } from 'react'
import { Link, Outlet, useLocation } from 'react-router-dom'
import { getLastRequestIds } from '../api/client'
import { Button, Code, TextInput } from '../components/primitives'
import { Icons } from '../components/ui/Icons'
type NavItem = {
label: string
to: string
icon: React.ComponentType<any>
}
const navItems: NavItem[] = [
{ label: 'Overview', to: '/' },
{ label: 'Tenants', to: '/tenants' },
{ label: 'Users', to: '/users' },
{ label: 'Sessions', to: '/sessions' },
{ label: 'Roles & Permissions', to: '/roles-permissions' },
{ label: 'Config', to: '/config' },
{ label: 'Definitions', to: '/definitions' },
{ label: 'Documents', to: '/documents' },
{ label: 'Scale & Placement', to: '/scale-placement' },
{ label: 'Deployments', to: '/deployments' },
{ label: 'Observability', to: '/observability' },
{ label: 'Platform Drift', to: '/drift' },
{ label: 'Audit Log', to: '/audit-log' },
{ label: 'Settings', to: '/settings' },
type NavCategory = {
label: string
items: NavItem[]
}
const navCategories: NavCategory[] = [
{
label: 'Platform',
items: [
{ label: 'Overview', to: '/', icon: Icons.Activity },
{ label: 'Tenants', to: '/tenants', icon: Icons.Tenants },
{ label: 'Fleet Nodes', to: '/fleet', icon: Icons.Fleet },
{ label: 'Observability', to: '/observability', icon: Icons.Observability },
],
},
{
label: 'Infrastructure',
items: [
{ label: 'Scale & Placement', to: '/scale-placement', icon: Icons.Topology },
{ label: 'Deployments', to: '/deployments', icon: Icons.Deployments },
],
},
{
label: 'Resources',
items: [
{ label: 'Config Registry', to: '/config', icon: Icons.Config },
{ label: 'Type Definitions', to: '/definitions', icon: Icons.Definitions },
{ label: 'Documents', to: '/documents', icon: Icons.Storage },
],
},
{
label: 'Governance',
items: [
{ label: 'Audit Log', to: '/audit-log', icon: Icons.Audit },
{ label: 'Platform Drift', to: '/drift', icon: Icons.Drift },
],
},
{
label: 'Management',
items: [
{ label: 'Users', to: '/users', icon: Icons.Users },
{ label: 'Sessions', to: '/sessions', icon: Icons.Sessions },
{ label: 'Roles & Permissions', to: '/roles-permissions', icon: Icons.ShieldAlert },
{ label: 'Settings', to: '/settings', icon: Icons.Settings },
],
},
]
function normalizePath(pathname: string) {
@@ -33,7 +66,7 @@ function normalizePath(pathname: string) {
export function Layout() {
const location = useLocation()
const active = normalizePath(location.pathname)
const activePath = normalizePath(location.pathname)
const [query, setQuery] = useState('')
const lastIds = getLastRequestIds()
@@ -74,111 +107,251 @@ export function Layout() {
}
}
return (
<div style={{ display: 'flex', minHeight: '100vh', fontFamily: 'system-ui, sans-serif' }}>
<aside
const NavLink = ({ item }: { item: NavItem }) => {
const active = activePath === normalizePath(item.to)
const Icon = item.icon
return (
<Link
to={item.to}
style={{
width: 260,
borderRight: '1px solid #eee',
padding: 16,
background: '#fafafa',
display: 'flex',
alignItems: 'center',
gap: '12px',
padding: '8px 12px',
borderRadius: 'var(--radius-md)',
color: active ? 'var(--text-primary)' : 'var(--text-secondary)',
background: active ? 'var(--bg-tertiary)' : 'transparent',
fontWeight: active ? 500 : 400,
textDecoration: 'none',
fontSize: '14px',
transition: 'all 0.2s ease',
}}
onMouseOver={(e) => {
if (!active) e.currentTarget.style.background = 'rgba(255,255,255,0.05)'
}}
onMouseOut={(e) => {
if (!active) e.currentTarget.style.background = 'transparent'
}}
>
<div style={{ fontWeight: 700, marginBottom: 16 }}>Cloudlysis Control</div>
<nav style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{navItems.map((item) => {
const isActive = active === normalizePath(item.to)
return (
<Link
key={item.to}
to={item.to}
style={{
textDecoration: 'none',
color: '#111',
padding: '6px 10px',
borderRadius: 8,
background: isActive ? '#eaeaea' : 'transparent',
}}
>
{item.label}
</Link>
)
})}
</nav>
</aside>
<span style={{ color: active ? 'var(--accent-primary)' : 'inherit', display: 'flex' }}>
<Icon />
</span>
{item.label}
</Link>
)
}
<div style={{ flex: 1 }}>
<header
return (
<div style={{ display: 'flex', minHeight: '100vh', background: 'var(--bg-primary)' }}>
{/* Sidebar */}
<aside
style={{
width: 240,
borderRight: '1px solid var(--border-primary)',
display: 'flex',
flexDirection: 'column',
background: 'var(--bg-primary)',
position: 'sticky',
top: 0,
height: '100vh',
zIndex: 10,
}}
>
<div
style={{
borderBottom: '1px solid #eee',
padding: 16,
padding: '24px',
borderBottom: '1px solid var(--border-primary)',
display: 'flex',
flexDirection: 'column',
gap: 10,
alignItems: 'center',
gap: '12px',
}}
>
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
<div style={{ width: 420, maxWidth: '100%' }}>
<div
style={{
width: 32,
height: 32,
background: 'var(--accent-primary)',
borderRadius: 'var(--radius-md)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#000',
}}
>
<Icons.Zap />
</div>
<span style={{ fontWeight: 700, fontSize: '18px', letterSpacing: '-0.02em' }}>
Cloudlysis
</span>
</div>
<nav
style={{
flex: 1,
padding: '20px 12px',
display: 'flex',
flexDirection: 'column',
gap: '20px',
overflowY: 'auto',
}}
>
{navCategories.map((cat) => (
<div key={cat.label} style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div
style={{
padding: '0 12px',
marginBottom: '4px',
fontSize: '11px',
fontWeight: 600,
color: 'var(--text-muted)',
textTransform: 'uppercase',
letterSpacing: '0.05em',
}}
>
{cat.label}
</div>
{cat.items.map((item) => (
<NavLink key={item.to} item={item} />
))}
</div>
))}
</nav>
<div
style={{
padding: '16px',
borderTop: '1px solid var(--border-primary)',
background: 'var(--bg-secondary)',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div
style={{
width: 32,
height: 32,
borderRadius: '50%',
background: 'var(--bg-tertiary)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '12px',
fontWeight: 600,
}}
>
OP
</div>
<div style={{ overflow: 'hidden' }}>
<div
style={{
fontSize: '13px',
fontWeight: 500,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
Operator
</div>
<div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Administrator</div>
</div>
</div>
</div>
</aside>
{/* Main Content */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
<header
style={{
borderBottom: '1px solid var(--border-primary)',
padding: '16px 32px',
display: 'flex',
flexDirection: 'column',
gap: '16px',
background: 'var(--bg-primary)',
position: 'sticky',
top: 0,
zIndex: 5,
}}
>
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ width: 480, maxWidth: '100%' }}>
<TextInput
ariaLabel="Global search"
placeholder="Search request/correlation/trace id"
placeholder="Search correlation/trace id..."
value={query}
onChange={setQuery}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (e.key === 'Enter') {
const id = query.trim()
if (!id) return
openGrafanaLogs(id)
}
}}
/>
</div>
<div style={{ display: 'flex', gap: '8px' }}>
<Button
variant="secondary"
size="sm"
onClick={() => {
const id = query.trim()
if (!id) return
openGrafanaLogs(id)
}
}}
/>
}}
disabled={!grafana.base}
icon={<Icons.Activity style={{ width: 14, height: 14 }} />}
>
Logs
</Button>
<Button
variant="secondary"
size="sm"
onClick={() => {
const id = query.trim()
if (!id) return
openGrafanaTrace(id)
}}
disabled={!grafana.base}
icon={<Icons.Topology style={{ width: 14, height: 14 }} />}
>
Trace
</Button>
</div>
<Button
onClick={() => {
const id = query.trim()
if (!id) return
openGrafanaLogs(id)
}}
disabled={!grafana.base}
>
Logs
</Button>
<Button
onClick={() => {
const id = query.trim()
if (!id) return
openGrafanaTrace(id)
}}
disabled={!grafana.base}
>
Trace
</Button>
</div>
{lastIds ? (
<div style={{ display: 'flex', gap: 16, alignItems: 'center', flexWrap: 'wrap' }}>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<span style={{ fontSize: 12, color: '#666' }}>request_id</span>
{lastIds && (
<div style={{ display: 'flex', gap: '24px', alignItems: 'center', flexWrap: 'wrap' }}>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<span style={{ fontSize: '12px', color: 'var(--text-muted)' }}>request_id</span>
<Code>{lastIds.requestId}</Code>
<Button onClick={() => copy(lastIds.requestId)}>Copy</Button>
<Button variant="ghost" size="sm" onClick={() => copy(lastIds.requestId)} style={{ padding: '2px 4px' }}>
Copy
</Button>
</div>
{lastIds.correlationId ? (
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<span style={{ fontSize: 12, color: '#666' }}>correlation_id</span>
{lastIds.correlationId && (
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<span style={{ fontSize: '12px', color: 'var(--text-muted)' }}>correlation_id</span>
<Code>{lastIds.correlationId}</Code>
<Button onClick={() => copy(lastIds.correlationId ?? '')}>Copy</Button>
<Button variant="ghost" size="sm" onClick={() => copy(lastIds.correlationId || '')} style={{ padding: '2px 4px' }}>
Copy
</Button>
<Button
onClick={() => openGrafanaLogs(lastIds.correlationId ?? '')}
variant="primary"
size="sm"
onClick={() => openGrafanaLogs(lastIds.correlationId || '')}
disabled={!grafana.base}
style={{ fontSize: '11px', padding: '2px 8px' }}
>
Investigate
</Button>
</div>
) : null}
)}
</div>
) : null}
)}
</header>
<Outlet />
<main style={{ padding: '32px', flex: 1 }}>
<Outlet />
</main>
</div>
</div>
)