ui: improve account/apps layout and lab status responsiveness
This commit is contained in:
parent
6b9aaa3e53
commit
35f7b77c1b
@ -60,6 +60,9 @@ def register(app) -> None:
|
||||
if cached and (now - float(_LAB_STATUS_CACHE.get("ts", 0.0)) < settings.LAB_STATUS_CACHE_SEC):
|
||||
return jsonify(cached)
|
||||
|
||||
t_total = time.perf_counter()
|
||||
timings_ms: dict[str, int] = {}
|
||||
|
||||
connected = False
|
||||
atlas_up = False
|
||||
atlas_known = False
|
||||
@ -71,7 +74,9 @@ def register(app) -> None:
|
||||
|
||||
# Atlas
|
||||
try:
|
||||
t_probe = time.perf_counter()
|
||||
atlas_grafana_ok = _http_ok(settings.GRAFANA_HEALTH_URL, expect_substring="ok")
|
||||
timings_ms["grafana"] = int((time.perf_counter() - t_probe) * 1000)
|
||||
if atlas_grafana_ok:
|
||||
connected = True
|
||||
atlas_up = True
|
||||
@ -82,7 +87,9 @@ def register(app) -> None:
|
||||
|
||||
if not atlas_known:
|
||||
try:
|
||||
t_probe = time.perf_counter()
|
||||
value = _vm_query("up")
|
||||
timings_ms["victoria_metrics"] = int((time.perf_counter() - t_probe) * 1000)
|
||||
if value is not None:
|
||||
connected = True
|
||||
atlas_known = True
|
||||
@ -93,7 +100,9 @@ def register(app) -> None:
|
||||
|
||||
# Oceanus (node-exporter direct probe)
|
||||
try:
|
||||
t_probe = time.perf_counter()
|
||||
if _http_ok(settings.OCEANUS_NODE_EXPORTER_URL):
|
||||
timings_ms["oceanus_node_exporter"] = int((time.perf_counter() - t_probe) * 1000)
|
||||
connected = True
|
||||
oceanus_known = True
|
||||
oceanus_up = True
|
||||
@ -101,14 +110,16 @@ def register(app) -> None:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
timings_ms["total"] = int((time.perf_counter() - t_total) * 1000)
|
||||
|
||||
payload = {
|
||||
"connected": connected,
|
||||
"atlas": {"up": atlas_up, "known": atlas_known, "source": atlas_source},
|
||||
"oceanus": {"up": oceanus_up, "known": oceanus_known, "source": oceanus_source},
|
||||
"checked_at": int(now),
|
||||
"timings_ms": timings_ms,
|
||||
}
|
||||
|
||||
_LAB_STATUS_CACHE["ts"] = now
|
||||
_LAB_STATUS_CACHE["value"] = payload
|
||||
return jsonify(payload)
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ VM_QUERY_TIMEOUT_SEC = float(os.getenv("VM_QUERY_TIMEOUT_SEC", "2"))
|
||||
HTTP_CHECK_TIMEOUT_SEC = float(os.getenv("HTTP_CHECK_TIMEOUT_SEC", "2"))
|
||||
K8S_API_TIMEOUT_SEC = float(os.getenv("K8S_API_TIMEOUT_SEC", "5"))
|
||||
LAB_STATUS_CACHE_SEC = float(os.getenv("LAB_STATUS_CACHE_SEC", "30"))
|
||||
GRAFANA_HEALTH_URL = os.getenv("GRAFANA_HEALTH_URL", "https://metrics.bstein.dev/api/health")
|
||||
GRAFANA_HEALTH_URL = os.getenv("GRAFANA_HEALTH_URL", "http://grafana.monitoring.svc.cluster.local/api/health")
|
||||
OCEANUS_NODE_EXPORTER_URL = os.getenv("OCEANUS_NODE_EXPORTER_URL", "http://192.168.22.24:9100/metrics")
|
||||
|
||||
AI_CHAT_API = os.getenv("AI_CHAT_API", "http://ollama.ai.svc.cluster.local:11434").rstrip("/")
|
||||
|
||||
@ -25,19 +25,33 @@ const networkData = ref(fallbackNetwork());
|
||||
const metricsData = ref(fallbackMetrics());
|
||||
|
||||
const statusLoading = ref(true);
|
||||
const statusFetching = ref(false);
|
||||
const statusError = ref("");
|
||||
|
||||
async function refreshLabStatus() {
|
||||
if (statusFetching.value) return;
|
||||
statusFetching.value = true;
|
||||
const controller = new AbortController();
|
||||
const timeoutId = window.setTimeout(() => controller.abort(), 6000);
|
||||
try {
|
||||
const resp = await fetch("/api/lab/status", { headers: { Accept: "application/json" } });
|
||||
const resp = await fetch("/api/lab/status", {
|
||||
headers: { Accept: "application/json" },
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (!resp.ok) throw new Error(`status ${resp.status}`);
|
||||
labStatus.value = await resp.json();
|
||||
statusError.value = "";
|
||||
} catch (err) {
|
||||
labStatus.value = null;
|
||||
statusError.value = "Live data unavailable";
|
||||
if (err?.name === "AbortError") {
|
||||
statusError.value = "Live data timed out";
|
||||
} else {
|
||||
statusError.value = "Live data unavailable";
|
||||
}
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId);
|
||||
statusLoading.value = false;
|
||||
statusFetching.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
</section>
|
||||
|
||||
<section v-if="auth.ready && auth.authenticated">
|
||||
<div class="grid two">
|
||||
<div class="account-grid">
|
||||
<div class="card module">
|
||||
<div class="module-head">
|
||||
<h2>Mail</h2>
|
||||
@ -123,6 +123,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="account-stack">
|
||||
<div class="card module">
|
||||
<div class="module-head">
|
||||
<h2>Vaultwarden</h2>
|
||||
@ -200,6 +201,7 @@
|
||||
<div v-if="jellyfin.syncDetail" class="hint mono jellyfin-detail">{{ jellyfin.syncDetail }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="admin.enabled" class="card module admin">
|
||||
<div class="module-head">
|
||||
@ -580,13 +582,24 @@ h1 {
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.grid.two {
|
||||
.account-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.account-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.account-stack .module {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.module {
|
||||
padding: 18px;
|
||||
}
|
||||
@ -716,9 +729,13 @@ button.primary {
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.grid.two {
|
||||
.account-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.account-stack .module {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
.admin {
|
||||
|
||||
@ -133,29 +133,14 @@ const sections = [
|
||||
description: "Build and ship: source control, CI, registry, and GitOps.",
|
||||
groups: [
|
||||
{
|
||||
title: "Source & CI",
|
||||
title: "Dev Stack",
|
||||
apps: [
|
||||
{ name: "Gitea", url: "https://scm.bstein.dev", target: "_blank", description: "Git hosting and collaboration." },
|
||||
{ name: "Jenkins", url: "https://ci.bstein.dev", target: "_blank", description: "CI pipelines and automation." },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Registry & Deploy",
|
||||
apps: [
|
||||
{ name: "Harbor", url: "https://registry.bstein.dev", target: "_blank", description: "Artifact registry." },
|
||||
{ name: "GitOps", url: "https://cd.bstein.dev", target: "_blank", description: "GitOps UI for Flux." },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Observability",
|
||||
apps: [
|
||||
{ name: "Grafana", url: "https://metrics.bstein.dev", target: "_blank", description: "Dashboards and monitoring." },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Infra & Secrets",
|
||||
apps: [
|
||||
{ name: "Vault", url: "https://secret.bstein.dev", target: "_blank", description: "Secrets management for infrastructure and apps." },
|
||||
{ name: "Grafana", url: "https://metrics.bstein.dev", target: "_blank", description: "Dashboards and monitoring." },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -216,6 +201,7 @@ const sections = [
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
margin-bottom: 14px;
|
||||
min-height: 92px;
|
||||
}
|
||||
|
||||
.group + .group {
|
||||
@ -232,6 +218,10 @@ const sections = [
|
||||
margin: 6px 0 0;
|
||||
color: var(--text-muted);
|
||||
max-width: 820px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tiles {
|
||||
@ -248,6 +238,7 @@ const sections = [
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
transition: border-color 160ms ease, transform 160ms ease;
|
||||
min-height: 112px;
|
||||
}
|
||||
|
||||
.tile:hover {
|
||||
@ -265,6 +256,10 @@ const sections = [
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user