Fix portal branding icons and system info
This commit is contained in:
parent
e4e126bd43
commit
2b03df0e3a
@ -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")
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user