Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | <script> import { onMount, onDestroy } from "svelte"; import { get } from "../lib/api.js"; let containers = $state([]); let repos = $state([]); let stats = $state(null); let loading = $state(true); async function load() { const [c, g, s] = await Promise.all([ get("/docker/containers"), get("/gitea/repos"), get("/system/stats"), ]); containers = c || []; repos = Array.isArray(g) ? g : g?.data || []; stats = s; loading = false; } function fmtBytes(b) { if (!b) return "0 B"; const u = ["B", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(b) / Math.log(1024)); return (b / Math.pow(1024, i)).toFixed(1) + " " + u[i]; } function pctColor(pct) { if (pct > 90) return "text-rose-500"; if (pct > 70) return "text-amber-500"; return "text-emerald-500"; } let interval; onMount(() => { load(); interval = setInterval(load, 10000); }); onDestroy(() => clearInterval(interval)); </script> <div class="space-y-8"> <div> <h1 class="text-2xl font-bold text-surface-900 dark:text-white tracking-tight">Overview</h1> <p class="text-sm text-surface-400 mt-1">System status at a glance</p> </div> {#if loading} <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> {#each Array(4) as _} <div class="h-[120px] rounded-xl bg-surface-100 dark:bg-surface-800 animate-pulse"></div> {/each} </div> {:else} <!-- Stats Cards --> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> <!-- CPU --> <div class="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-5 shadow-sm hover:shadow-md transition-shadow duration-200"> <div class="flex items-center justify-between mb-3"> <p class="text-xs font-semibold uppercase tracking-wider text-surface-400">CPU</p> <div class="w-8 h-8 rounded-lg bg-sky-50 flex items-center justify-center"> <svg class="w-4 h-4 text-sky-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg> </div> </div> <p class="text-2xl font-bold {pctColor(stats?.cpu_percent || 0)}">{stats?.cpu_percent || 0}%</p> <p class="text-xs text-surface-400 mt-1">{stats?.cpu_count || 0} cores ยท Load {stats?.load_avg?.[0] || 0}</p> </div> <!-- Memory --> <div class="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-5 shadow-sm hover:shadow-md transition-shadow duration-200"> <div class="flex items-center justify-between mb-3"> <p class="text-xs font-semibold uppercase tracking-wider text-surface-400">Memory</p> <div class="w-8 h-8 rounded-lg bg-violet-50 flex items-center justify-center"> <svg class="w-4 h-4 text-violet-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> </div> </div> <p class="text-2xl font-bold {pctColor(stats?.memory?.percent || 0)}">{stats?.memory?.percent || 0}%</p> <p class="text-xs text-surface-400 mt-1">{fmtBytes(stats?.memory?.used)} / {fmtBytes(stats?.memory?.total)}</p> </div> <!-- Disk --> <div class="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-5 shadow-sm hover:shadow-md transition-shadow duration-200"> <div class="flex items-center justify-between mb-3"> <p class="text-xs font-semibold uppercase tracking-wider text-surface-400">Disk</p> <div class="w-8 h-8 rounded-lg bg-amber-50 flex items-center justify-center"> <svg class="w-4 h-4 text-amber-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" /></svg> </div> </div> <p class="text-2xl font-bold {pctColor(stats?.disk?.percent || 0)}">{stats?.disk?.percent || 0}%</p> <p class="text-xs text-surface-400 mt-1">{fmtBytes(stats?.disk?.free)} free of {fmtBytes(stats?.disk?.total)}</p> </div> <!-- Uptime --> <div class="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-5 shadow-sm hover:shadow-md transition-shadow duration-200"> <div class="flex items-center justify-between mb-3"> <p class="text-xs font-semibold uppercase tracking-wider text-surface-400">Uptime</p> <div class="w-8 h-8 rounded-lg bg-emerald-50 flex items-center justify-center"> <svg class="w-4 h-4 text-emerald-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg> </div> </div> <p class="text-2xl font-bold text-surface-800 dark:text-surface-100">{stats?.uptime || "โ"}</p> <p class="text-xs text-surface-400 mt-1 truncate" title={stats?.platform}>{stats?.platform || "โ"}</p> </div> </div> <!-- Services Grid --> <div class="grid grid-cols-1 lg:grid-cols-2 gap-4"> <!-- Docker Summary --> <div class="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-5 shadow-sm"> <div class="flex items-center justify-between mb-4"> <h3 class="text-sm font-semibold text-surface-700 dark:text-surface-200">Docker Containers</h3> <span class="text-xs font-medium text-emerald-600 bg-emerald-50 px-2 py-0.5 rounded-full">{containers.filter(c => c.status === "running").length}/{containers.length} running</span> </div> <div class="space-y-2"> {#each containers.slice(0, 5) as c} <div class="flex items-center justify-between py-1.5 text-sm"> <div class="flex items-center gap-2"> <span class="w-1.5 h-1.5 rounded-full {c.status !== 'running' ? 'bg-surface-300' : c.health === 'unhealthy' ? 'bg-rose-400' : c.health === 'healthy' ? 'bg-emerald-400' : 'bg-sky-400'}"></span> <span class="font-medium text-surface-700 dark:text-surface-200">{c.name}</span> </div> <span class="text-xs text-surface-400 truncate max-w-[180px]">{c.image}</span> </div> {/each} {#if containers.length > 5} <p class="text-xs text-primary-500 font-medium pt-1">+{containers.length - 5} more</p> {/if} </div> </div> <!-- Repos Summary --> <div class="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-5 shadow-sm"> <div class="flex items-center justify-between mb-4"> <h3 class="text-sm font-semibold text-surface-700 dark:text-surface-200">Git Repositories</h3> <span class="text-xs font-medium text-primary-600 bg-primary-50 px-2 py-0.5 rounded-full">{repos.length} repos</span> </div> <div class="space-y-2"> {#each repos.slice(0, 5) as r} <div class="flex items-center justify-between py-1.5 text-sm"> <div class="flex items-center gap-2"> <svg class="w-3.5 h-3.5 text-surface-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /></svg> <span class="font-medium text-surface-700 dark:text-surface-200">{r.name}</span> </div> <span class="text-xs text-surface-400">{r.language || "โ"}</span> </div> {/each} {#if repos.length > 5} <p class="text-xs text-primary-500 font-medium pt-1">+{repos.length - 5} more</p> {/if} </div> </div> </div> <!-- Storage Usage --> {#if stats?.volumes?.length} <div class="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-5 shadow-sm"> <h3 class="text-sm font-semibold text-surface-700 dark:text-surface-200 mb-3">Storage Usage</h3> <div class="space-y-3"> {#each stats.volumes as vol} <div> <div class="flex justify-between text-xs text-surface-500 mb-1"> <span class="font-medium">{vol.mount}</span> <span>{vol.percent}%</span> </div> <div class="w-full h-3 bg-surface-100 dark:bg-surface-700 rounded-full overflow-hidden"> <div class="h-full rounded-full transition-all duration-700 ease-out {vol.percent > 90 ? 'bg-gradient-to-r from-rose-400 to-rose-500' : vol.percent > 70 ? 'bg-gradient-to-r from-amber-400 to-amber-500' : 'bg-gradient-to-r from-emerald-400 to-emerald-500'}" style="width: {vol.percent}%" ></div> </div> <div class="flex justify-between mt-1 text-xs text-surface-400"> <span>{fmtBytes(vol.used)} used</span> <span>{fmtBytes(vol.free)} free</span> </div> </div> {/each} </div> </div> {/if} {/if} </div> |