// Shell: topbar + navbar + sidebar + page header.
// Translations via window.t() (see i18n.js).
const { useState, useEffect, useRef, useMemo } = React;
const Topbar = ({ theme, setTheme, searchValue, onSearchChange, userBadge }) => {
const t = window.t;
return (
acme-prod
onSearchChange(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Escape') onSearchChange(''); }}
style={{background:'transparent', border:0, color:'var(--text-1)', outline:'none', flex:1, minWidth:0, fontSize:12, padding:0, marginLeft:2}}
/>
{searchValue ? (
) : ⌘K}
{(userBadge || 'NR').slice(0, 2).toUpperCase()}
);
};
const Navbar = ({ activeTab, setActiveTab }) => {
const t = window.t;
const tabs = [
{ id: 'Overview', label: t('nav.overview') },
{ id: 'Providers', label: t('nav.providers') },
{ id: 'Models', label: t('nav.models') },
{ id: 'Policies', label: t('nav.policies') },
{ id: 'API keys', label: t('nav.apiKeys') },
{ id: 'Playground', label: t('nav.playground') },
{ id: 'Analytics', label: t('nav.analytics') },
{ id: 'Logs', label: t('nav.logs') },
];
return (
{tabs.map(tab => (
))}
openai-compat
·
v2.3.1
);
};
const Sidebar = ({ activeTab, setActiveTab, providers }) => {
const t = window.t;
const modelsCount = providers.reduce((a, p) => a + (p.models || 0), 0);
const enabled = providers.filter(p => p.enabled).length;
const groups = [
{ group: t('side.workspace'), items: [
{ id: 'Overview', icon: 'overview', label: t('nav.overview') },
{ id: 'Providers', icon: 'providers', label: t('nav.providers'), count: enabled },
{ id: 'Models', icon: 'models', label: t('nav.models'), count: modelsCount },
{ id: '__deploy', icon: 'cube', label: t('side.deployments'), count: 4 },
]},
{ group: t('side.routing'), items: [
{ id: 'Policies', icon: 'policies', label: t('nav.policies'), count: 5 },
{ id: '__rate', icon: 'filter', label: t('side.rateLimits') },
{ id: '__exp', icon: 'flask', label: t('side.experiments'), count: 2 },
]},
{ group: t('side.developer'), items: [
{ id: 'API keys', icon: 'keys', label: t('nav.apiKeys'), count: 12 },
{ id: 'Playground', icon: 'playground', label: t('nav.playground') },
{ id: 'Logs', icon: 'logs', label: t('nav.logs') },
]},
{ group: t('side.observability'), items: [
{ id: 'Analytics', icon: 'analytics', label: t('nav.analytics') },
{ id: '__spend', icon: 'dollar', label: t('side.spend') },
{ id: '__alerts', icon: 'bell', label: t('topbar.alerts'), count: 2 },
]},
];
return (
);
};
const PageHeader = ({ title, timeRange, setTimeRange, onAddProvider, onDeploy, onExport }) => {
const t = window.t;
const ranges = ['1h', '24h', '7d', '30d'];
return (
{title || t('page.overview')}
{t('page.liveSync')}
·
acme-prod / openai-compat
·
{t('page.region')}: us-east-1
{ranges.map(r => (
))}
);
};
Object.assign(window, { Topbar, Navbar, Sidebar, PageHeader });