Fix portal branding icons and system info

This commit is contained in:
Jino Jose 2026-06-30 16:29:03 +05:30
parent e4e126bd43
commit 2b03df0e3a
3 changed files with 129 additions and 38 deletions

View File

@ -1621,14 +1621,40 @@ async def system_info(user: dict = Depends(current_user)):
except Exception:
v = "unknown"
try:
os_name = subprocess.check_output(["lsb_release", "-ds"], text=True, timeout=3).strip().strip('"')
except Exception:
os_name = subprocess.getoutput("uname -sr") or "unknown"
ip_address = ""
try:
for _name, addrs in psutil.net_if_addrs().items():
for addr in addrs:
if addr.address.count(".") == 3 and not addr.address.startswith("127."):
ip_address = addr.address
raise StopIteration
except StopIteration:
pass
except Exception:
ip_address = ""
metrics = collect_metrics()
gpu_name = subprocess.getoutput(
"nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1"
) or "N/A"
return {
"hostname": hostname,
"ip_address": ip_address,
"os": os_name,
"uptime": uptime,
"python_version": subprocess.getoutput("python3 --version"),
"ollama_version": v,
"gpu_name": subprocess.getoutput(
"nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1"
) or "N/A",
"gpu_name": gpu_name,
"gpu_vram_gb": metrics.get("gpu_mem_total_gb"),
"ram_total_gb": metrics.get("ram_total_gb"),
"disk_total_gb": metrics.get("disk_total_gb"),
"cpu_cores": metrics.get("cpu_cores"),
}
@app.get("/api/system/feasibility")

View File

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus One AI Portal</title>
<link rel="stylesheet" href="style.css?v=10">
<script src="https://unpkg.com/lucide@latest"></script>
</head>
<body>
@ -126,7 +125,7 @@
<div class="stat-label">AI Tools</div>
</div>
<div class="stat-item">
<div class="stat-num">96 GB</div>
<div class="stat-num" id="stat-vram"></div>
<div class="stat-label">GPU VRAM</div>
</div>
<div class="stat-item" style="border-right:none">
@ -163,27 +162,27 @@
<div class="sysinfo-specs-title">Server Specifications</div>
<div class="sysinfo-spec-row">
<span class="sysinfo-spec-label">Hostname</span>
<span class="sysinfo-spec-value" id="spec-hostname">ai-server</span>
<span class="sysinfo-spec-value" id="spec-hostname">Detecting…</span>
</div>
<div class="sysinfo-spec-row">
<span class="sysinfo-spec-label">IP Address</span>
<span class="sysinfo-spec-value" id="spec-ip">192.168.1.100</span>
<span class="sysinfo-spec-value" id="spec-ip">Detecting…</span>
</div>
<div class="sysinfo-spec-row">
<span class="sysinfo-spec-label">GPU</span>
<span class="sysinfo-spec-value" id="spec-gpu">NVIDIA RTX Pro 6000 (96 GB VRAM)</span>
<span class="sysinfo-spec-value" id="spec-gpu">Detecting…</span>
</div>
<div class="sysinfo-spec-row">
<span class="sysinfo-spec-label">System RAM</span>
<span class="sysinfo-spec-value" id="spec-ram">128 GB DDR5</span>
<span class="sysinfo-spec-value" id="spec-ram">Detecting…</span>
</div>
<div class="sysinfo-spec-row">
<span class="sysinfo-spec-label">Storage</span>
<span class="sysinfo-spec-value" id="spec-storage">4 TB NVMe SSD</span>
<span class="sysinfo-spec-value" id="spec-storage">Detecting…</span>
</div>
<div class="sysinfo-spec-row">
<span class="sysinfo-spec-label">OS</span>
<span class="sysinfo-spec-value" id="spec-os">Ubuntu 22.04 LTS</span>
<span class="sysinfo-spec-value" id="spec-os">Detecting…</span>
</div>
<div class="sysinfo-spec-row">
<span class="sysinfo-spec-label">Tier</span>
@ -743,14 +742,11 @@ const set = (id, val) => { const el = document.getElementById(id); if (el) el.te
// Realistic mock metrics for demo / offline use
// Slight random drift so numbers feel live
function mockMetrics() {
const base = { cpu: 23, gpu: 67, gtemp: 72, ram: 41, disk: 28 };
const drift = v => Math.max(5, Math.min(95, v + Math.round((Math.random() - 0.5) * 6)));
return {
cpu_pct: drift(base.cpu), gpu_pct: drift(base.gpu),
gpu_temp: drift(base.gtemp), ram_pct: drift(base.ram),
ram_used_gb: 52, ram_total_gb: 128,
disk_pct: base.disk, disk_used_gb: 1.1, disk_total_gb: 4.0,
uptime: '14 days, 6 hrs'
cpu_pct: null, gpu_pct: null, gpu_temp: null, ram_pct: null,
ram_used_gb: null, ram_total_gb: null,
disk_pct: null, disk_used_gb: null, disk_total_gb: null,
uptime: 'Unavailable'
};
}
@ -774,30 +770,49 @@ function applyMetrics(d) {
async function fetchMetrics() {
try {
const d = await fetch('/api/metrics', { credentials: 'include' }).then(r => r.json());
const d = await fetch('/api/metrics', { credentials: 'include' }).then(r => {
if (!r.ok) throw new Error('metrics unavailable');
return r.json();
});
applyMetrics(d);
const notice = document.getElementById('metrics-notice');
if (notice) notice.style.display = 'none';
} catch {
// Backend offline — show realistic mock so dashboard looks live
applyMetrics(mockMetrics());
const notice = document.getElementById('metrics-notice');
if (notice) notice.style.display = 'none'; // hide warning in demo mode
if (notice) notice.style.display = 'flex';
}
}
async function fetchSystemInfo() {
try {
const [info, metrics] = await Promise.all([
fetch('/api/system/info', { credentials: 'include' }).then(r => r.json()),
fetch('/api/metrics', { credentials: 'include' }).then(r => r.json()),
fetch('/api/system/info', { credentials: 'include' }).then(r => {
if (!r.ok) throw new Error('system info unavailable');
return r.json();
}),
fetch('/api/metrics', { credentials: 'include' }).then(r => {
if (!r.ok) throw new Error('metrics unavailable');
return r.json();
}),
]);
set('spec-hostname', info.hostname || '—');
if (info.gpu_name && info.gpu_name !== 'N/A') set('spec-gpu', info.gpu_name);
if (metrics.ram_total_gb) set('spec-ram', metrics.ram_total_gb + ' GB DDR5');
if (metrics.disk_total_gb) set('spec-storage', metrics.disk_total_gb + ' TB NVMe SSD');
set('spec-ip', info.ip_address || '—');
set('spec-os', info.os || '—');
const gpuName = info.gpu_name && info.gpu_name !== 'N/A' ? info.gpu_name : null;
const gpuVram = info.gpu_vram_gb || metrics.gpu_mem_total_gb;
set('spec-gpu', gpuName ? gpuName + (gpuVram ? ' (' + gpuVram + ' GB VRAM)' : '') : 'None detected');
set('stat-vram', gpuVram ? gpuVram + ' GB' : '—');
set('spec-ram', (info.ram_total_gb || metrics.ram_total_gb) ? (info.ram_total_gb || metrics.ram_total_gb) + ' GB' : '—');
set('spec-storage', (info.disk_total_gb || metrics.disk_total_gb) ? (info.disk_total_gb || metrics.disk_total_gb) + ' GB' : '—');
} catch {
// API offline — keep the crisp hardcoded defaults (already accurate for Entry tier)
set('spec-hostname', 'Unavailable');
set('spec-ip', 'Unavailable');
set('spec-gpu', 'Unavailable');
set('spec-ram', 'Unavailable');
set('spec-storage', 'Unavailable');
set('spec-os', 'Unavailable');
set('stat-vram', '—');
}
}
@ -807,7 +822,47 @@ setInterval(fetchMetrics, 30000);
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
<script>lucide.createIcons();</script>
<script>
function renderLocalIcons() {
const paths = {
bell: '<path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/>',
cpu: '<rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 15h3M1 9h3M1 15h3"/>',
activity: '<path d="M22 12h-4l-3 8L9 4l-3 8H2"/>',
thermometer: '<path d="M14 14.8V5a2 2 0 0 0-4 0v9.8a4 4 0 1 0 4 0z"/>',
'memory-stick': '<path d="M6 19V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v14"/><path d="M8 7h8M8 11h8M8 15h8M4 19h16"/>',
'hard-drive': '<path d="M22 12H2l3-7h14l3 7z"/><path d="M5 12v7h14v-7"/><circle cx="17" cy="16" r="1"/>',
clock: '<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>',
zap: '<path d="M13 2 3 14h8l-1 8 10-12h-8l1-8z"/>',
'message-square': '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
'message-circle': '<path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 0 1-.9-3.8 8.5 8.5 0 1 1 17 0z"/>',
'file-search': '<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"/><circle cx="11" cy="14" r="2"/><path d="m13 16 2 2"/>',
bot: '<rect x="5" y="8" width="14" height="10" rx="2"/><path d="M12 8V4M8 12h.01M16 12h.01M9 16h6"/>',
'code-2': '<path d="m18 16 4-4-4-4M6 8l-4 4 4 4M14.5 4l-5 16"/>',
plug: '<path d="M12 22v-5M9 8V2M15 8V2M7 8h10v4a5 5 0 0 1-10 0V8z"/>',
gauge: '<path d="M12 14l4-4"/><path d="M3.3 19a10 10 0 1 1 17.4 0"/>',
'git-branch': '<circle cx="6" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M6 9v12M9 6h3a6 6 0 0 1 6 6v3"/>',
target: '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>',
lock: '<rect x="3" y="11" width="18" height="10" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
default: '<circle cx="12" cy="12" r="9"/><path d="M12 8v4l3 2"/>'
};
document.querySelectorAll('i[data-lucide]').forEach(el => {
const name = el.getAttribute('data-lucide') || 'default';
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '1.75');
svg.setAttribute('stroke-linecap', 'round');
svg.setAttribute('stroke-linejoin', 'round');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('style', el.getAttribute('style') || 'width:16px;height:16px');
svg.innerHTML = paths[name] || paths.default;
el.replaceWith(svg);
});
}
renderLocalIcons();
</script>
<script>
(function() {
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

View File

@ -6,7 +6,16 @@
<title>Sign In — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<style>
body { background: var(--navy); min-height: 100vh; display: flex; align-items: center; justify-content: center; }
body {
background:
radial-gradient(ellipse 80% 55% at 15% 5%, rgba(124,58,237,.18) 0%, transparent 60%),
radial-gradient(ellipse 60% 50% at 85% 95%, rgba(99,102,241,.14) 0%, transparent 60%),
linear-gradient(160deg, #f8f7ff 0%, #eef2ff 65%, #f5f0ff 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-wrap {
width: 100%;
@ -21,21 +30,22 @@
.login-brand-name {
font-size: 28px;
font-weight: 800;
color: white;
color: var(--text-primary);
letter-spacing: .5px;
}
.login-brand-name span { color: var(--teal); }
.login-brand-name span { color: var(--brand); }
.login-brand-sub {
font-size: 13px;
color: rgba(255,255,255,.45);
color: var(--text-tertiary);
margin-top: 6px;
}
.login-card {
background:var(--navy2);
border: 1px solid var(--border);
border-radius: 16px;
padding: 36px 32px;
box-shadow: 0 24px 60px rgba(0,0,0,.4);
box-shadow: 0 24px 60px rgba(15,23,42,.16);
}
.login-title {
@ -71,13 +81,13 @@
}
.login-input:focus {
border-color: var(--teal);
box-shadow: 0 0 0 3px rgba(13,148,136,.12);
box-shadow: var(--shadow-focus);
}
.login-btn {
width: 100%;
padding: 13px;
background: var(--teal);
background: var(--brand);
color: white;
border: none;
border-radius: 8px;
@ -87,7 +97,7 @@
margin-top: 8px;
transition: background .15s, transform .1s;
}
.login-btn:hover { background: #0B7A70; }
.login-btn:hover { background: var(--brand-hover); }
.login-btn:active { transform: scale(.98); }
.login-btn:disabled { background: var(--xlt); cursor: not-allowed; }
@ -107,9 +117,9 @@
text-align: center;
margin-top: 28px;
font-size: 12px;
color: rgba(255,255,255,.3);
color: var(--text-tertiary);
}
.login-footer a { color: rgba(255,255,255,.5); }
.login-footer a { color: var(--text-secondary); }
.login-secure {
display: flex;