296 lines
15 KiB
HTML
296 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Health Center — Nexus One AI</title>
|
|
<link rel="stylesheet" href="style.css?v=12">
|
|
<style>
|
|
/* ── Health Center layout ──────────────────────────────────────── */
|
|
.hc-summary {
|
|
display: flex; align-items: center; gap: 14px;
|
|
background: var(--surface); border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg); padding: 20px 24px; margin-bottom: 24px;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
.hc-summary-dot { width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0; }
|
|
.hc-summary-dot.loading { background: var(--border-strong); }
|
|
.hc-summary-dot.healthy { background: var(--green); box-shadow: 0 0 0 5px rgba(22,163,74,.15); }
|
|
.hc-summary-dot.warn { background: var(--amber); box-shadow: 0 0 0 5px rgba(217,119,6,.15); }
|
|
.hc-summary-dot.crit { background: var(--red); box-shadow: 0 0 0 5px rgba(220,38,38,.15); animation: hc-pulse 1.6s ease-in-out infinite; }
|
|
@keyframes hc-pulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }
|
|
.hc-summary-text { flex: 1; }
|
|
.hc-summary-title { font-size: 16px; font-weight: 800; color: var(--text-primary); }
|
|
.hc-summary-sub { font-size: 13px; color: var(--text-tertiary); margin-top: 2px; }
|
|
.hc-refresh-btn {
|
|
padding: 9px 16px; border-radius: var(--radius-md); border: 1px solid var(--border);
|
|
background: var(--surface); color: var(--text-secondary); font-family: inherit;
|
|
font-size: 13px; font-weight: 600; cursor: pointer; transition: .15s;
|
|
}
|
|
.hc-refresh-btn:hover { border-color: var(--brand); color: var(--brand); }
|
|
.hc-updated { font-size: 12px; color: var(--text-tertiary); margin-top: 10px; }
|
|
|
|
.hc-grid {
|
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px;
|
|
}
|
|
@media(max-width: 900px) { .hc-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
@media(max-width: 600px) { .hc-grid { grid-template-columns: 1fr; } }
|
|
|
|
.hc-card {
|
|
background: var(--surface); border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg); padding: 18px 20px;
|
|
display: flex; gap: 14px; align-items: flex-start;
|
|
}
|
|
.hc-card-icon {
|
|
width: 36px; height: 36px; border-radius: var(--radius-md); flex-shrink: 0;
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.hc-card-icon svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
|
|
.hc-card.ok .hc-card-icon { background: var(--green-lt); color: var(--green); }
|
|
.hc-card.warning .hc-card-icon { background: var(--amber-lt); color: var(--amber); }
|
|
.hc-card.critical .hc-card-icon{ background: var(--red-lt); color: var(--red); }
|
|
.hc-card.loading .hc-card-icon { background: var(--bg); color: var(--text-tertiary); }
|
|
|
|
.hc-card-body { flex: 1; min-width: 0; }
|
|
.hc-card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
|
|
.hc-card-name { font-size: 13.5px; font-weight: 700; color: var(--text-primary); }
|
|
.hc-card-status {
|
|
font-size: .6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
|
|
padding: 2px 9px; border-radius: var(--radius-pill); flex-shrink: 0;
|
|
}
|
|
.hc-card.ok .hc-card-status { background: var(--green-lt); color: var(--green); }
|
|
.hc-card.warning .hc-card-status { background: var(--amber-lt); color: var(--amber); }
|
|
.hc-card.critical .hc-card-status { background: var(--red-lt); color: var(--red); }
|
|
.hc-card.loading .hc-card-status { background: var(--bg); color: var(--text-tertiary); }
|
|
.hc-card-detail { font-size: 12.5px; color: var(--text-secondary); line-height: 1.5; }
|
|
|
|
.hc-unavailable {
|
|
display: flex; align-items: flex-start; gap: 10px;
|
|
background: var(--amber-lt); border: 1px solid var(--amber-border);
|
|
border-radius: var(--radius-md); padding: 14px 16px; margin-bottom: 20px;
|
|
color: var(--amber); font-size: 13px; line-height: 1.5;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body data-role="admin">
|
|
|
|
<header class="topnav">
|
|
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
|
|
<nav>
|
|
<a href="index.html">Home</a>
|
|
<a href="quickstart.html">Quick Start</a>
|
|
<a href="prompts.html">Prompt Library</a>
|
|
<a href="usecases.html">Use Cases</a>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn">AI Tools ▾</button>
|
|
<div class="nav-drop-menu">
|
|
<span class="nav-drop-cat">INTELLIGENCE /</span>
|
|
<a href="documents.html">Document Intelligence</a>
|
|
<a href="chat-multi.html">AI Workspace</a>
|
|
<a href="prompt-studio.html">Prompt Studio</a>
|
|
<a href="meeting.html">Meeting Assistant</a>
|
|
<span class="nav-drop-cat">AUTOMATION /</span>
|
|
<a href="agents.html">Agent Studio</a>
|
|
<a href="schedules.html">Scheduled Jobs</a>
|
|
<a href="workflows.html">Workflow Designer</a>
|
|
<span class="nav-drop-cat">QUALITY /</span>
|
|
<a href="evals.html">AI Eval Suite</a>
|
|
<a href="chatrooms.html">Chat Rooms</a>
|
|
</div>
|
|
</div>
|
|
<span class="nav-sep"></span>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn">Help ▾</button>
|
|
<div class="nav-drop-menu">
|
|
<span class="nav-drop-cat">LEARN /</span>
|
|
<a href="quickstart.html">Quick Start</a>
|
|
<a href="models.html">Models</a>
|
|
<span class="nav-drop-cat">SUPPORT /</span>
|
|
<a href="troubleshooting.html">Troubleshoot</a>
|
|
<a href="faq.html">FAQ</a>
|
|
<span class="nav-drop-cat">MORE /</span>
|
|
<a href="glossary.html">Glossary</a>
|
|
<a href="whats-new.html">What's New</a>
|
|
</div>
|
|
</div>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn active">Admin ▾</button>
|
|
<div class="nav-drop-menu nav-drop-menu-wide">
|
|
<span class="nav-drop-cat">DOCS /</span>
|
|
<a href="security.html">Security & Privacy</a>
|
|
<a href="admin.html">Admin Guide</a>
|
|
<span class="nav-drop-cat">MONITOR /</span>
|
|
<a href="dashboard.html">Dashboard</a>
|
|
<a href="analytics.html">Usage Analytics</a>
|
|
<a href="audit.html">Governance & Audit</a>
|
|
<a href="health.html" class="active">Health Center</a>
|
|
<a href="feedback.html">Feedback & Ratings</a>
|
|
<span class="nav-drop-cat">MANAGE /</span>
|
|
<a href="users.html">Users</a>
|
|
<a href="teams.html">Teams</a>
|
|
<a href="models-admin.html">Model Catalog</a>
|
|
<a href="training.html">Training</a>
|
|
<a href="knowledge.html">Knowledge Base</a>
|
|
<span class="nav-drop-cat">TOOLS /</span>
|
|
<a href="apikeys.html">API Keys</a>
|
|
<a href="benchmark.html">Benchmarking</a>
|
|
<a href="model-compare.html">Model Compare</a>
|
|
<a href="api-playground.html">API Playground</a>
|
|
<a href="guardrails.html">Guardrails</a>
|
|
<a href="rag-quality.html">RAG Quality</a>
|
|
<a href="router.html">Model Router</a>
|
|
<a href="connectors.html">MCP & Enterprise Connectors</a>
|
|
<span class="nav-drop-cat">SYSTEM /</span>
|
|
<a href="appliance.html">Appliance Ops</a>
|
|
<a href="console.html">Console</a>
|
|
<a href="settings.html">Settings</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<div class="nav-right">
|
|
<div id="nav-org-logo" class="nav-org-logo"></div>
|
|
<span class="nav-tier-badge" data-brand="tier" data-tier-slug="basic">Basic Tier</span>
|
|
<a href="notifications.html" class="nav-icon-btn" title="Notifications">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="page-hero">
|
|
<div class="eyebrow">Admin Dashboard</div>
|
|
<h1>Health Center</h1>
|
|
<p>Unified status for every subsystem Nexus One AI depends on — GPU, drivers, CUDA, models, portal, API, scheduler, storage, backup, license, SSL and database.</p>
|
|
</div>
|
|
|
|
<div class="content">
|
|
|
|
<div class="hc-summary">
|
|
<div class="hc-summary-dot loading" id="hc-summary-dot"></div>
|
|
<div class="hc-summary-text">
|
|
<div class="hc-summary-title" id="hc-summary-title">Checking system health…</div>
|
|
<div class="hc-summary-sub" id="hc-summary-sub">Contacting the API</div>
|
|
</div>
|
|
<button class="hc-refresh-btn" onclick="loadHealth()">Refresh</button>
|
|
</div>
|
|
|
|
<div class="hc-unavailable" id="hc-unavailable" style="display:none">
|
|
<span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span>
|
|
<span>Couldn't reach <code>/api/health</code>. The API itself may be down — check that the cezen-api service is running.</span>
|
|
</div>
|
|
|
|
<div class="hc-grid" id="hc-grid"></div>
|
|
|
|
<div class="hc-updated" id="hc-updated"></div>
|
|
|
|
</div>
|
|
|
|
<footer>
|
|
<p>Nexus One AI · Powered by Cezen · <span data-brand="tier">Basic Tier</span></p>
|
|
<p>Questions? <a href="mailto:support@cezentech.com">support@cezentech.com</a> · <a href="https://cezentech.com" target="_blank">cezentech.com</a></p>
|
|
</footer>
|
|
|
|
<script>
|
|
const _API = '/api';
|
|
|
|
const CHECK_META = {
|
|
gpu: { label: 'GPU', icon: '<path d="M2 7h20v10H2z"/><path d="M6 7V5M10 7V5M14 7V5M18 7V5M6 17v2M10 17v2M14 17v2M18 17v2"/>' },
|
|
drivers: { label: 'Drivers', icon: '<path d="M12 2v4"/><path d="M12 18v4"/><path d="M4.93 4.93l2.83 2.83"/><path d="M16.24 16.24l2.83 2.83"/><path d="M2 12h4"/><path d="M18 12h4"/><path d="M4.93 19.07l2.83-2.83"/><path d="M16.24 7.76l2.83-2.83"/>' },
|
|
cuda: { label: 'CUDA', icon: '<rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/>' },
|
|
models: { label: 'Models', icon: '<circle cx="12" cy="12" r="10"/><path d="M12 8v4l3 2"/>' },
|
|
portal: { label: 'Portal', icon: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18"/>' },
|
|
api: { label: 'API', icon: '<path d="M4 17V7a2 2 0 0 1 2-2h4l2 2h6a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z"/>' },
|
|
scheduler: { label: 'Scheduler', icon: '<rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/>' },
|
|
storage: { label: 'Storage', icon: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/><path d="M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3"/>' },
|
|
backup: { label: 'Backup', icon: '<polyline points="20 7 20 3 4 3 4 7"/><path d="M20 21H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2z"/><path d="M12 12v6M9 15l3-3 3 3"/>' },
|
|
license: { label: 'License', icon: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>' },
|
|
ssl: { label: 'SSL', icon: '<rect x="3" y="11" width="18" height="10" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>' },
|
|
database: { label: 'Database', icon: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v6c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/><path d="M3 11v8c0 1.66 4.03 3 9 3s9-1.34 9-3v-8"/>' },
|
|
};
|
|
const CHECK_ORDER = ['api','portal','database','scheduler','models','gpu','drivers','cuda','storage','backup','license','ssl'];
|
|
|
|
function escHtml(s) { return (s==null?'':String(s)).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
|
|
function renderLoadingGrid() {
|
|
document.getElementById('hc-grid').innerHTML = CHECK_ORDER.map(key => {
|
|
const meta = CHECK_META[key];
|
|
return `<div class="hc-card loading">
|
|
<div class="hc-card-icon"><svg viewBox="0 0 24 24">${meta.icon}</svg></div>
|
|
<div class="hc-card-body">
|
|
<div class="hc-card-top"><span class="hc-card-name">${meta.label}</span><span class="hc-card-status">Checking</span></div>
|
|
<div class="hc-card-detail">Loading…</div>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
async function loadHealth() {
|
|
renderLoadingGrid();
|
|
document.getElementById('hc-unavailable').style.display = 'none';
|
|
try {
|
|
const res = await fetch(`${_API}/health`, { credentials: 'include' });
|
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
const data = await res.json();
|
|
renderSummary(data.status);
|
|
renderGrid(data.checks || {});
|
|
document.getElementById('hc-updated').textContent = 'Updated ' + new Date(data.generated_at || Date.now()).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'});
|
|
} catch (e) {
|
|
document.getElementById('hc-unavailable').style.display = '';
|
|
renderSummary(null);
|
|
document.getElementById('hc-grid').innerHTML = '';
|
|
document.getElementById('hc-updated').textContent = '';
|
|
}
|
|
}
|
|
|
|
function renderSummary(status) {
|
|
const dot = document.getElementById('hc-summary-dot');
|
|
const title = document.getElementById('hc-summary-title');
|
|
const sub = document.getElementById('hc-summary-sub');
|
|
if (window.CezenExperience) {
|
|
const mapped = status === 'ok' ? 'ready' : status === 'warning' ? 'degraded' : status === 'critical' ? 'failed' : 'unavailable';
|
|
title.className = 'hc-summary-title cx-state cx-state-' + mapped;
|
|
title.setAttribute('role', mapped === 'failed' ? 'alert' : 'status');
|
|
}
|
|
if (status === 'ok') {
|
|
dot.className = 'hc-summary-dot healthy';
|
|
title.textContent = 'Healthy — all systems normal';
|
|
sub.textContent = 'Every subsystem below is reporting OK.';
|
|
} else if (status === 'warning') {
|
|
dot.className = 'hc-summary-dot warn';
|
|
title.textContent = 'Warning — some items need attention';
|
|
sub.textContent = 'Review the amber cards below.';
|
|
} else if (status === 'critical') {
|
|
dot.className = 'hc-summary-dot crit';
|
|
title.textContent = 'Critical — action required';
|
|
sub.textContent = 'Review the red cards below as soon as possible.';
|
|
} else {
|
|
dot.className = 'hc-summary-dot loading';
|
|
title.textContent = 'Health data unavailable';
|
|
sub.textContent = 'Could not reach the API.';
|
|
}
|
|
}
|
|
|
|
function renderGrid(checks) {
|
|
const statusLabel = { ok: 'Healthy', warning: 'Warning', critical: 'Critical' };
|
|
document.getElementById('hc-grid').innerHTML = CHECK_ORDER.map(key => {
|
|
const meta = CHECK_META[key];
|
|
const c = checks[key] || { status: 'warning', detail: 'No data returned for this check.' };
|
|
const cls = ['ok','warning','critical'].includes(c.status) ? c.status : 'warning';
|
|
return `<div class="hc-card ${cls}">
|
|
<div class="hc-card-icon"><svg viewBox="0 0 24 24">${meta.icon}</svg></div>
|
|
<div class="hc-card-body">
|
|
<div class="hc-card-top"><span class="hc-card-name">${meta.label}</span><span class="hc-card-status">${statusLabel[cls]}</span></div>
|
|
<div class="hc-card-detail">${escHtml(c.detail || '')}</div>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
loadHealth();
|
|
setInterval(loadHealth, 30000);
|
|
</script>
|
|
<script src="auth.js"></script>
|
|
<script src="experience.js?v=1"></script>
|
|
<script src="branding.js?v=3"></script>
|
|
</body>
|
|
</html>
|