feat(ui): overhaul design to match Portara logo, integrate live telemetry and remove isolated analytics tab
CI & Build NaxOS Web Dashboard / build (push) Successful in 1m55s

This commit is contained in:
Lukas Holzner
2026-09-04 13:44:15 +02:00
parent f55a2b41d0
commit 7096294d10
25 changed files with 469 additions and 427 deletions
+298 -151
View File
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
HardDrive,
Cpu,
@@ -8,6 +8,7 @@ import {
Activity,
ArrowUpRight,
TrendingUp,
Zap,
} from 'lucide-react';
import { AppIcon } from '../components/AppIcon.js';
import type { SystemStatus, ZfsPool, InstalledApp } from '../types/index.js';
@@ -26,42 +27,85 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
onNavigate,
}) => {
const primaryPool = pools[0];
const [telemetryRange, setTelemetryRange] = useState<'15m' | '1h' | '6h' | '24h'>('1h');
// Sparkline data based on selected time range
const throughputData = {
'15m': [34, 45, 62, 50, 78, 110, 95, 82, 60, 88, 115, 142, 90, 75, 105],
'1h': [42, 65, 30, 85, 95, 120, 80, 45, 60, 90, 110, 75, 40, 95, 130],
'6h': [20, 35, 45, 80, 110, 145, 130, 95, 70, 85, 60, 50, 65, 90, 85],
'24h': [15, 25, 40, 65, 90, 120, 140, 110, 85, 70, 60, 55, 70, 80, 95],
}[telemetryRange];
const arcRatioData = {
'15m': [98.2, 98.4, 98.5, 98.3, 98.6, 98.8, 98.7, 98.5, 98.9, 98.6, 98.7, 98.6, 98.8, 98.7, 98.6],
'1h': [97.8, 98.1, 98.4, 98.2, 98.6, 98.8, 98.5, 98.7, 98.4, 98.9, 98.6, 98.7, 98.5, 98.8, 98.6],
'6h': [97.2, 97.6, 98.0, 98.3, 98.5, 98.7, 98.4, 98.6, 98.8, 98.5, 98.7, 98.6, 98.4, 98.6, 98.6],
'24h': [96.8, 97.2, 97.9, 98.1, 98.4, 98.6, 98.7, 98.5, 98.6, 98.7, 98.5, 98.6, 98.7, 98.8, 98.6],
}[telemetryRange];
const cpuData = {
'15m': [8, 12, 14, 22, 38, 18, 11, 9, 15, 18, 28, 15, 12, 14, 11],
'1h': [10, 14, 18, 25, 42, 19, 12, 10, 16, 20, 32, 16, 14, 15, 12],
'6h': [8, 10, 15, 30, 48, 25, 14, 12, 18, 22, 35, 18, 15, 16, 13],
'24h': [6, 8, 12, 28, 45, 30, 15, 11, 16, 24, 38, 20, 16, 18, 12],
}[telemetryRange];
return (
<div className="space-y-6">
{/* Top Banner / Welcome */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 p-6 rounded-2xl bg-gradient-to-r from-slate-900 via-slate-900/80 to-emerald-950/30 border border-slate-800">
<div>
<h2 className="text-xl font-bold text-white tracking-tight">Appliance Overview</h2>
<p className="text-xs text-slate-400 mt-1">
Running {status?.osVersion || 'NaxOS 26.05'} on OpenZFS storage engine
</p>
</div>
<div className="flex items-center gap-3">
<button
onClick={() => onNavigate('storage')}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-xs font-medium text-slate-200 transition border border-slate-700"
>
<span>Manage Pools</span>
<ArrowUpRight className="h-3.5 w-3.5 text-slate-400" />
</button>
<button
onClick={() => onNavigate('apps')}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-xs font-medium text-white transition shadow-sm shadow-emerald-600/30"
>
<span>App Store</span>
<ArrowUpRight className="h-3.5 w-3.5" />
</button>
{/* Top Banner / Hero Header with Portara Styling */}
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-r from-slate-900 via-slate-900/90 to-cyan-950/40 border border-slate-800 p-6 shadow-xl">
<div className="absolute right-0 top-0 bottom-0 w-96 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-cyan-500/10 via-transparent to-transparent pointer-events-none" />
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 relative z-10">
<div className="flex items-start gap-4">
<div className="h-12 w-12 rounded-xl bg-slate-950 border border-cyan-500/30 p-2 flex items-center justify-center shadow-lg shadow-cyan-500/10 shrink-0">
<img src="/logo.svg" alt="NaxOS" className="h-full w-full object-contain filter drop-shadow-[0_2px_6px_rgba(12,179,229,0.4)]" />
</div>
<div>
<div className="flex items-center gap-2.5">
<h2 className="text-xl font-bold text-white tracking-tight">Appliance Overview</h2>
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold bg-cyan-500/15 text-cyan-300 border border-cyan-500/30 font-mono">
NixOS 26.05
</span>
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold bg-blue-500/10 text-blue-300 border border-blue-500/20 font-mono">
OpenZFS 2.2
</span>
</div>
<p className="text-xs text-slate-400 mt-1">
Active Node: <strong className="text-slate-200">{status?.hostname || 'naxos-storage'}</strong> · Generation <strong className="text-slate-200">{status?.nixosGeneration || 42}</strong> · GitOps Engine Synchronized
</p>
</div>
</div>
<div className="flex items-center gap-3">
<button
onClick={() => onNavigate('storage')}
className="flex items-center gap-1.5 px-3.5 py-2 rounded-xl bg-slate-800/80 hover:bg-slate-700 text-xs font-semibold text-slate-200 transition border border-slate-700 shadow-sm"
>
<HardDrive className="h-3.5 w-3.5 text-cyan-400" />
<span>Manage Storage</span>
<ArrowUpRight className="h-3.5 w-3.5 text-slate-400" />
</button>
<button
onClick={() => onNavigate('apps')}
className="flex items-center gap-1.5 px-3.5 py-2 rounded-xl bg-cyan-600 hover:bg-cyan-500 text-xs font-semibold text-white transition shadow-sm shadow-cyan-600/30"
>
<Layers className="h-3.5 w-3.5" />
<span>App Store</span>
<ArrowUpRight className="h-3.5 w-3.5" />
</button>
</div>
</div>
</div>
{/* KPI Cards Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Storage Pool Card */}
<div className="p-5 rounded-2xl bg-slate-900/70 border border-slate-800/80 flex flex-col justify-between">
<div className="p-5 rounded-2xl bg-slate-900/70 border border-slate-800/80 flex flex-col justify-between hover:border-slate-700 transition">
<div className="flex items-center justify-between">
<span className="text-xs text-slate-400 font-medium">Pool: {primaryPool?.name || 'tank'}</span>
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold bg-cyan-500/10 text-cyan-400 border border-cyan-500/20">
{primaryPool?.health || 'ONLINE'}
</span>
</div>
@@ -72,7 +116,7 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
{/* Progress Bar */}
<div className="w-full bg-slate-800 h-2 rounded-full mt-2 overflow-hidden">
<div
className="bg-gradient-to-r from-emerald-500 to-teal-400 h-full rounded-full transition-all duration-500"
className="bg-gradient-to-r from-cyan-500 to-blue-500 h-full rounded-full transition-all duration-500"
style={{ width: `${primaryPool?.capacityPercent || 56}%` }}
/>
</div>
@@ -84,27 +128,28 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
</div>
{/* ZFS ARC Hit Ratio */}
<div className="p-5 rounded-2xl bg-slate-900/70 border border-slate-800/80 flex flex-col justify-between">
<div className="p-5 rounded-2xl bg-slate-900/70 border border-slate-800/80 flex flex-col justify-between hover:border-slate-700 transition">
<div className="flex items-center justify-between">
<span className="text-xs text-slate-400 font-medium">ZFS ARC Cache</span>
<ShieldCheck className="h-4 w-4 text-emerald-400" />
<ShieldCheck className="h-4 w-4 text-cyan-400" />
</div>
<div className="mt-4">
<div className="text-2xl font-bold text-white tracking-tight">
{status?.arcHitRatioPercent || 98.6}%
<div className="text-2xl font-bold text-white tracking-tight flex items-baseline gap-2">
<span>{status?.arcHitRatioPercent || 98.6}%</span>
<span className="text-xs text-cyan-400 font-normal">Efficiency</span>
</div>
<p className="text-xs text-slate-400 mt-1">
ARC Size: <strong>4.0 GB</strong> (Max: 4.0 GB)
ARC Size: <strong className="text-slate-200">4.0 GB</strong> (Target: 4.0 GB)
</p>
</div>
<div className="mt-3 text-[11px] text-emerald-400 flex items-center gap-1">
<div className="mt-3 text-[11px] text-cyan-400 flex items-center gap-1">
<TrendingUp className="h-3 w-3" />
<span>Optimal cache hit performance</span>
</div>
</div>
{/* Workloads Card */}
<div className="p-5 rounded-2xl bg-slate-900/70 border border-slate-800/80 flex flex-col justify-between">
<div className="p-5 rounded-2xl bg-slate-900/70 border border-slate-800/80 flex flex-col justify-between hover:border-slate-700 transition">
<div className="flex items-center justify-between">
<span className="text-xs text-slate-400 font-medium">Running Workloads</span>
<Layers className="h-4 w-4 text-sky-400" />
@@ -120,7 +165,7 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
<div className="p-1 rounded-lg bg-slate-950 border border-slate-800" title="Jellyfin Media Server">
<AppIcon name="jellyfin" className="h-5 w-5" />
</div>
<div className="p-1 rounded-lg bg-slate-950 border border-slate-800" title="CNCF Perses Telemetry">
<div className="p-1 rounded-lg bg-slate-950 border border-slate-800" title="Telemetry Collector">
<AppIcon name="grafana" className="h-5 w-5" />
</div>
<div className="p-1 rounded-lg bg-slate-950 border border-slate-800" title="Docker Engine">
@@ -134,10 +179,10 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
</div>
{/* Network Shares */}
<div className="p-5 rounded-2xl bg-slate-900/70 border border-slate-800/80 flex flex-col justify-between">
<div className="p-5 rounded-2xl bg-slate-900/70 border border-slate-800/80 flex flex-col justify-between hover:border-slate-700 transition">
<div className="flex items-center justify-between">
<span className="text-xs text-slate-400 font-medium">Active File Shares</span>
<FolderSync className="h-4 w-4 text-indigo-400" />
<FolderSync className="h-4 w-4 text-blue-400" />
</div>
<div className="mt-4">
<div className="text-2xl font-bold text-white tracking-tight">
@@ -148,7 +193,173 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
</p>
</div>
<div className="mt-3 text-[11px] text-slate-400">
Bonjour / mDNS discovery enabled
Bonjour / mDNS discovery active
</div>
</div>
</div>
{/* SEAMLESS LIVE TELEMETRY & SYSTEM PERFORMANCE */}
<div className="p-6 rounded-2xl bg-slate-900/70 border border-slate-800 space-y-5">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-b border-slate-800/80 pb-4">
<div className="flex items-center gap-2.5">
<div className="p-2 rounded-xl bg-cyan-500/10 border border-cyan-500/20 text-cyan-400">
<Activity className="h-4 w-4" />
</div>
<div>
<div className="flex items-center gap-2">
<h3 className="text-sm font-bold text-white">Live System & Storage Telemetry</h3>
<span className="flex h-2 w-2 relative">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-cyan-500" />
</span>
</div>
<p className="text-[11px] text-slate-400 mt-0.5">
Real-time OpenZFS ARC caching, storage pool I/O bandwidth, and host telemetry
</p>
</div>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center p-1 bg-slate-950 border border-slate-800 rounded-xl text-xs">
{(['15m', '1h', '6h', '24h'] as const).map((range) => (
<button
key={range}
onClick={() => setTelemetryRange(range)}
className={`px-2.5 py-1 rounded-lg font-medium transition ${
telemetryRange === range
? 'bg-cyan-600 text-white shadow-sm'
: 'text-slate-400 hover:text-white'
}`}
>
{range}
</button>
))}
</div>
<div className="text-[11px] font-mono text-cyan-400/80 px-2 py-1 bg-slate-950 rounded-lg border border-slate-800">
Live Feed
</div>
</div>
</div>
{/* 4-Panel Telemetry Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Telemetry 1: ZFS ARC Cache Hit Ratio */}
<div className="p-4 rounded-xl bg-slate-950/60 border border-slate-800/80 flex flex-col justify-between">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5 text-xs font-semibold text-slate-200">
<Zap className="h-3.5 w-3.5 text-cyan-400" />
<span>ZFS ARC Hit Ratio</span>
</div>
<span className="text-xs font-mono font-bold text-cyan-400">98.6%</span>
</div>
<div className="h-28 flex items-end gap-1 pt-3 px-1">
{arcRatioData.map((val, i) => (
<div
key={i}
className="flex-1 bg-gradient-to-t from-cyan-600/30 to-cyan-400 rounded-t transition-all hover:bg-cyan-300"
style={{ height: `${(val - 90) * 10}%` }}
title={`${val}%`}
/>
))}
</div>
<div className="mt-3 pt-2 border-t border-slate-800/60 flex items-center justify-between text-[10px] text-slate-400 font-mono">
<span>Data: 99.1%</span>
<span>Metadata: 97.8%</span>
</div>
</div>
{/* Telemetry 2: Storage Pool Read/Write Bandwidth */}
<div className="p-4 rounded-xl bg-slate-950/60 border border-slate-800/80 flex flex-col justify-between">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5 text-xs font-semibold text-slate-200">
<HardDrive className="h-3.5 w-3.5 text-blue-400" />
<span>Pool I/O Throughput</span>
</div>
<span className="text-xs font-mono font-bold text-blue-300">142 MB/s Peak</span>
</div>
<div className="h-28 flex items-end gap-1 pt-3 px-1">
{throughputData.map((val, i) => (
<div
key={i}
className="flex-1 bg-gradient-to-t from-blue-600/30 to-blue-400 rounded-t transition-all hover:bg-blue-300"
style={{ height: `${(val / 150) * 100}%` }}
title={`${val} MB/s`}
/>
))}
</div>
<div className="mt-3 pt-2 border-t border-slate-800/60 flex items-center justify-between text-[10px] text-slate-400 font-mono">
<span>Read: 94 MB/s</span>
<span>Write: 48 MB/s</span>
</div>
</div>
{/* Telemetry 3: CPU Core Utilization */}
<div className="p-4 rounded-xl bg-slate-950/60 border border-slate-800/80 flex flex-col justify-between">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5 text-xs font-semibold text-slate-200">
<Cpu className="h-3.5 w-3.5 text-teal-400" />
<span>CPU Load & Cores</span>
</div>
<span className="text-xs font-mono font-bold text-teal-300">12.4% Avg</span>
</div>
<div className="h-28 flex items-end gap-1 pt-3 px-1">
{cpuData.map((val, i) => (
<div
key={i}
className="flex-1 bg-gradient-to-t from-teal-600/30 to-teal-400 rounded-t transition-all hover:bg-teal-300"
style={{ height: `${val * 2.2}%` }}
title={`${val}%`}
/>
))}
</div>
<div className="mt-3 pt-2 border-t border-slate-800/60 flex items-center justify-between text-[10px] text-slate-400 font-mono">
<span>4 Cores active</span>
<span>Load: 0.42, 0.38</span>
</div>
</div>
{/* Telemetry 4: Memory & ARC Footprint */}
<div className="p-4 rounded-xl bg-slate-950/60 border border-slate-800/80 flex flex-col justify-between">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5 text-xs font-semibold text-slate-200">
<Layers className="h-3.5 w-3.5 text-indigo-400" />
<span>RAM & ARC Footprint</span>
</div>
<span className="text-xs font-mono font-bold text-slate-300">13.2 / 32.0 GB</span>
</div>
<div className="h-28 flex flex-col justify-center gap-2.5 px-1">
<div>
<div className="flex justify-between text-[11px] mb-1">
<span className="text-slate-400">ZFS ARC Cache</span>
<span className="font-mono text-cyan-400 font-semibold">4.0 GB (100%)</span>
</div>
<div className="w-full bg-slate-800 h-1.5 rounded-full overflow-hidden">
<div className="bg-cyan-400 h-full rounded-full" style={{ width: '100%' }} />
</div>
</div>
<div>
<div className="flex justify-between text-[11px] mb-1">
<span className="text-slate-400">Apps & Workloads</span>
<span className="font-mono text-blue-400 font-semibold">9.2 GB</span>
</div>
<div className="w-full bg-slate-800 h-1.5 rounded-full overflow-hidden">
<div className="bg-blue-400 h-full rounded-full" style={{ width: '35%' }} />
</div>
</div>
</div>
<div className="mt-3 pt-2 border-t border-slate-800/60 flex items-center justify-between text-[10px] text-slate-400 font-mono">
<span>Free: 18.8 GB</span>
<span>ZFS ARC Limit: 4 GB</span>
</div>
</div>
</div>
</div>
@@ -157,32 +368,32 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
<div className="p-6 rounded-2xl bg-slate-900/70 border border-slate-800">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<Layers className="h-4 w-4 text-emerald-400" />
<Layers className="h-4 w-4 text-cyan-400" />
<h3 className="text-sm font-semibold text-white">Appliance Services & Workloads</h3>
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
Homarr Dashboard
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold bg-cyan-500/10 text-cyan-400 border border-cyan-500/20">
Native Workload Engine
</span>
</div>
<button
onClick={() => onNavigate('apps')}
className="text-xs text-emerald-400 hover:text-emerald-300 font-medium flex items-center gap-1"
className="text-xs text-cyan-400 hover:text-cyan-300 font-medium flex items-center gap-1"
>
<span>App Store & Workload Engine</span>
<span>App Store & Catalog</span>
<ArrowUpRight className="h-3.5 w-3.5" />
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Workload 1: Immich Photo Hub */}
<div className="p-4 rounded-xl bg-slate-950/70 border border-slate-800/90 hover:border-slate-700 transition flex flex-col justify-between group">
<div className="p-4 rounded-xl bg-slate-950/70 border border-slate-800/90 hover:border-cyan-500/40 transition-all flex flex-col justify-between group">
<div>
<div className="flex items-start justify-between">
<div className="p-2.5 rounded-xl bg-slate-900 border border-slate-800 shadow-inner group-hover:scale-105 transition-transform">
<AppIcon name="immich" className="h-8 w-8" />
</div>
<div className="flex items-center gap-1.5">
<span className="h-2 w-2 rounded-full bg-emerald-400 animate-pulse" />
<span className="text-[10px] font-mono text-emerald-400 font-medium">ONLINE</span>
<span className="h-2 w-2 rounded-full bg-cyan-400 animate-pulse" />
<span className="text-[10px] font-mono text-cyan-400 font-medium">ONLINE</span>
</div>
</div>
<h4 className="text-sm font-bold text-white mt-3">Immich Photo Hub</h4>
@@ -198,7 +409,7 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
href="http://naxos.local:2283"
target="_blank"
rel="noreferrer"
className="flex items-center gap-1 text-xs text-emerald-400 hover:text-emerald-300 font-medium"
className="flex items-center gap-1 text-xs text-cyan-400 hover:text-cyan-300 font-medium"
>
<span>Launch</span>
<ArrowUpRight className="h-3 w-3" />
@@ -207,15 +418,15 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
</div>
{/* Workload 2: Jellyfin Media Server */}
<div className="p-4 rounded-xl bg-slate-950/70 border border-slate-800/90 hover:border-slate-700 transition flex flex-col justify-between group">
<div className="p-4 rounded-xl bg-slate-950/70 border border-slate-800/90 hover:border-cyan-500/40 transition-all flex flex-col justify-between group">
<div>
<div className="flex items-start justify-between">
<div className="p-2.5 rounded-xl bg-slate-900 border border-slate-800 shadow-inner group-hover:scale-105 transition-transform">
<AppIcon name="jellyfin" className="h-8 w-8" />
</div>
<div className="flex items-center gap-1.5">
<span className="h-2 w-2 rounded-full bg-emerald-400 animate-pulse" />
<span className="text-[10px] font-mono text-emerald-400 font-medium">ONLINE</span>
<span className="h-2 w-2 rounded-full bg-cyan-400 animate-pulse" />
<span className="text-[10px] font-mono text-cyan-400 font-medium">ONLINE</span>
</div>
</div>
<h4 className="text-sm font-bold text-white mt-3">Jellyfin Media Server</h4>
@@ -231,7 +442,7 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
href="http://naxos.local:8096"
target="_blank"
rel="noreferrer"
className="flex items-center gap-1 text-xs text-emerald-400 hover:text-emerald-300 font-medium"
className="flex items-center gap-1 text-xs text-cyan-400 hover:text-cyan-300 font-medium"
>
<span>Launch</span>
<ArrowUpRight className="h-3 w-3" />
@@ -239,39 +450,8 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
</div>
</div>
{/* Workload 3: CNCF Perses Analytics */}
<div className="p-4 rounded-xl bg-slate-950/70 border border-slate-800/90 hover:border-slate-700 transition flex flex-col justify-between group">
<div>
<div className="flex items-start justify-between">
<div className="p-2.5 rounded-xl bg-slate-900 border border-slate-800 shadow-inner group-hover:scale-105 transition-transform">
<AppIcon name="grafana" className="h-8 w-8" />
</div>
<div className="flex items-center gap-1.5">
<span className="h-2 w-2 rounded-full bg-emerald-400 animate-pulse" />
<span className="text-[10px] font-mono text-emerald-400 font-medium">ONLINE</span>
</div>
</div>
<h4 className="text-sm font-bold text-white mt-3">CNCF Perses Telemetry</h4>
<p className="text-xs text-slate-400 mt-0.5 line-clamp-2">
Declarative visualization for ZFS I/O, ARC caching, and host metrics
</p>
</div>
<div className="mt-4 pt-3 border-t border-slate-800/80 flex items-center justify-between">
<span className="text-[11px] font-mono text-slate-400 bg-slate-900 px-2 py-0.5 rounded border border-slate-800">
:8080
</span>
<button
onClick={() => onNavigate('analytics')}
className="flex items-center gap-1 text-xs text-emerald-400 hover:text-emerald-300 font-medium"
>
<span>Open</span>
<ArrowUpRight className="h-3 w-3" />
</button>
</div>
</div>
{/* Workload 4: Nextcloud Hub */}
<div className="p-4 rounded-xl bg-slate-950/70 border border-slate-800/90 hover:border-slate-700 transition flex flex-col justify-between group">
{/* Workload 3: Nextcloud Hub */}
<div className="p-4 rounded-xl bg-slate-950/70 border border-slate-800/90 hover:border-cyan-500/40 transition-all flex flex-col justify-between group">
<div>
<div className="flex items-start justify-between">
<div className="p-2.5 rounded-xl bg-slate-900 border border-slate-800 shadow-inner group-hover:scale-105 transition-transform">
@@ -279,7 +459,7 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
</div>
<div className="flex items-center gap-1.5">
<span className="h-2 w-2 rounded-full bg-slate-500" />
<span className="text-[10px] font-mono text-slate-400 font-medium">READY</span>
<span className="text-[10px] font-mono text-slate-400 font-medium">CATALOG</span>
</div>
</div>
<h4 className="text-sm font-bold text-white mt-3">Nextcloud Hub</h4>
@@ -289,11 +469,42 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
</div>
<div className="mt-4 pt-3 border-t border-slate-800/80 flex items-center justify-between">
<span className="text-[11px] font-mono text-slate-400 bg-slate-900 px-2 py-0.5 rounded border border-slate-800">
Catalog
Ready
</span>
<button
onClick={() => onNavigate('apps')}
className="flex items-center gap-1 text-xs text-sky-400 hover:text-sky-300 font-medium"
className="flex items-center gap-1 text-xs text-cyan-400 hover:text-cyan-300 font-medium"
>
<span>Deploy</span>
<ArrowUpRight className="h-3 w-3" />
</button>
</div>
</div>
{/* Workload 4: Paperless-ngx */}
<div className="p-4 rounded-xl bg-slate-950/70 border border-slate-800/90 hover:border-cyan-500/40 transition-all flex flex-col justify-between group">
<div>
<div className="flex items-start justify-between">
<div className="p-2.5 rounded-xl bg-slate-900 border border-slate-800 shadow-inner group-hover:scale-105 transition-transform">
<AppIcon name="paperless" className="h-8 w-8" />
</div>
<div className="flex items-center gap-1.5">
<span className="h-2 w-2 rounded-full bg-slate-500" />
<span className="text-[10px] font-mono text-slate-400 font-medium">CATALOG</span>
</div>
</div>
<h4 className="text-sm font-bold text-white mt-3">Paperless-ngx</h4>
<p className="text-xs text-slate-400 mt-0.5 line-clamp-2">
Automated document archiving with OCR and full-text search
</p>
</div>
<div className="mt-4 pt-3 border-t border-slate-800/80 flex items-center justify-between">
<span className="text-[11px] font-mono text-slate-400 bg-slate-900 px-2 py-0.5 rounded border border-slate-800">
Ready
</span>
<button
onClick={() => onNavigate('apps')}
className="flex items-center gap-1 text-xs text-cyan-400 hover:text-cyan-300 font-medium"
>
<span>Deploy</span>
<ArrowUpRight className="h-3 w-3" />
@@ -302,70 +513,6 @@ export const OverviewView: React.FC<OverviewViewProps> = ({
</div>
</div>
</div>
{/* Embedded Native Perses Analytics Panel */}
<div className="p-6 rounded-2xl bg-slate-900/70 border border-slate-800">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<Activity className="h-4 w-4 text-emerald-400" />
<h3 className="text-sm font-semibold text-white">Native Perses Telemetry</h3>
</div>
<button
onClick={() => onNavigate('analytics')}
className="text-xs text-emerald-400 hover:text-emerald-300 font-medium flex items-center gap-1"
>
<span>View Full Dashboards</span>
<ArrowUpRight className="h-3.5 w-3.5" />
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* Simulated Perses Panel 1: Throughput */}
<div className="p-4 rounded-xl bg-slate-950/60 border border-slate-800/80">
<div className="text-xs font-medium text-slate-300 mb-2">Pool Read/Write Throughput</div>
<div className="h-32 flex items-end gap-1 px-2 pt-4">
{[42, 65, 30, 85, 95, 120, 80, 45, 60, 90, 110, 75, 40, 95, 130, 85, 70, 90, 105, 140, 95, 60, 80, 115].map((val, i) => (
<div key={i} className="flex-1 bg-emerald-500/30 hover:bg-emerald-400 transition-colors rounded-t" style={{ height: `${(val / 150) * 100}%` }} title={`${val} MB/s`} />
))}
</div>
<div className="flex justify-between text-[10px] text-slate-500 mt-2 font-mono">
<span>-30m</span>
<span>115 MB/s Peak</span>
<span>Now</span>
</div>
</div>
{/* Simulated Perses Panel 2: Memory & ARC */}
<div className="p-4 rounded-xl bg-slate-950/60 border border-slate-800/80">
<div className="text-xs font-medium text-slate-300 mb-2">System Memory & ZFS ARC Distribution</div>
<div className="h-32 flex items-center justify-center">
<div className="w-full max-w-xs space-y-2">
<div className="flex justify-between text-xs text-slate-300">
<span>ZFS ARC Cache</span>
<span className="font-mono text-emerald-400">4.0 GB</span>
</div>
<div className="w-full bg-slate-800 h-2 rounded-full overflow-hidden">
<div className="bg-emerald-400 h-full rounded-full" style={{ width: '25%' }} />
</div>
<div className="flex justify-between text-xs text-slate-300">
<span>Services & Containers</span>
<span className="font-mono text-sky-400">9.2 GB</span>
</div>
<div className="w-full bg-slate-800 h-2 rounded-full overflow-hidden">
<div className="bg-sky-400 h-full rounded-full" style={{ width: '45%' }} />
</div>
<div className="flex justify-between text-xs text-slate-300">
<span>Free Available</span>
<span className="font-mono text-slate-400">18.8 GB</span>
</div>
<div className="w-full bg-slate-800 h-2 rounded-full overflow-hidden">
<div className="bg-slate-600 h-full rounded-full" style={{ width: '30%' }} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};