Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
44bda19da5 |
@@ -1,63 +0,0 @@
|
|||||||
// static/js/ui.js
|
|
||||||
(() => {
|
|
||||||
const docEl = document.documentElement;
|
|
||||||
|
|
||||||
// ---- THEME ----
|
|
||||||
const THEME_KEY = "pve-ui-theme";
|
|
||||||
const storedTheme = localStorage.getItem(THEME_KEY);
|
|
||||||
if (storedTheme === "light" || storedTheme === "dark") {
|
|
||||||
docEl.setAttribute("data-bs-theme", storedTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
const btnTheme = document.getElementById("btnTheme");
|
|
||||||
if (btnTheme) {
|
|
||||||
btnTheme.addEventListener("click", () => {
|
|
||||||
const current = docEl.getAttribute("data-bs-theme") === "light" ? "dark" : "light";
|
|
||||||
docEl.setAttribute("data-bs-theme", current);
|
|
||||||
localStorage.setItem(THEME_KEY, current);
|
|
||||||
btnTheme.innerHTML = current === "dark"
|
|
||||||
? '<i class="bi bi-moon-stars"></i> Theme'
|
|
||||||
: '<i class="bi bi-sun"></i> Theme';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- DENSITY ----
|
|
||||||
const DENSITY_KEY = "pve-ui-density";
|
|
||||||
const storedDensity = localStorage.getItem(DENSITY_KEY);
|
|
||||||
if (storedDensity === "compact") {
|
|
||||||
docEl.setAttribute("data-density", "compact");
|
|
||||||
}
|
|
||||||
|
|
||||||
const btnDensity = document.getElementById("btnDensity");
|
|
||||||
if (btnDensity) {
|
|
||||||
btnDensity.addEventListener("click", () => {
|
|
||||||
const isCompact = docEl.getAttribute("data-density") === "compact";
|
|
||||||
if (isCompact) {
|
|
||||||
docEl.removeAttribute("data-density");
|
|
||||||
localStorage.setItem(DENSITY_KEY, "normal");
|
|
||||||
} else {
|
|
||||||
docEl.setAttribute("data-density", "compact");
|
|
||||||
localStorage.setItem(DENSITY_KEY, "compact");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- PRE blocks: keep nicely scrollable ----
|
|
||||||
const pres = document.querySelectorAll("pre.pre-scrollable");
|
|
||||||
pres.forEach((p) => {
|
|
||||||
p.style.maxHeight = "40vh";
|
|
||||||
p.style.overflow = "auto";
|
|
||||||
});
|
|
||||||
|
|
||||||
// Optional: subtle overflow shadows for horizontally scrollable areas
|
|
||||||
const scrollables = document.querySelectorAll(".table-responsive, .overflow-auto");
|
|
||||||
scrollables.forEach((el) => {
|
|
||||||
el.addEventListener("scroll", () => {
|
|
||||||
// Add classes for left/right shadow indicators
|
|
||||||
const atStart = el.scrollLeft <= 0;
|
|
||||||
const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 1;
|
|
||||||
el.classList.toggle("is-scrolled-start", !atStart);
|
|
||||||
el.classList.toggle("is-scrolled-end", !atEnd);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
|
@@ -1,132 +1,167 @@
|
|||||||
/* ========== Root, theme & density ========== */
|
/* Dark theme */
|
||||||
:root {
|
body {
|
||||||
--radius: 14px;
|
background-color: #0f1115;
|
||||||
--shadow-sm: 0 4px 12px rgba(0, 0, 0, .08);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-density="compact"] .table-sm> :not(caption)>*>* {
|
.card.health-card {
|
||||||
padding: .3rem .4rem;
|
background: #101520;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-density="compact"] .input-group-sm>.form-control,
|
.health-dot {
|
||||||
html[data-density="compact"] .form-select,
|
|
||||||
html[data-density="compact"] .btn-sm {
|
|
||||||
padding-top: .2rem;
|
|
||||||
padding-bottom: .2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rounded cards / sections */
|
|
||||||
.card {
|
|
||||||
border-radius: var(--radius);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion .accordion-item {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion-button {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navbar polish */
|
|
||||||
.navbar .navbar-brand i {
|
|
||||||
font-size: 1.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Health dot — controlled by JS via .ok / .bad */
|
|
||||||
.health-card .health-dot {
|
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--bs-secondary-color);
|
background: #dc3545;
|
||||||
/* neutral by default */
|
|
||||||
box-shadow: 0 0 0 4px rgba(0, 0, 0, .12) inset, 0 0 10px rgba(0, 0, 0, .1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* green when OK, red when problem */
|
.health-dot.ok {
|
||||||
.health-card .health-dot.ok {
|
background: #28a745;
|
||||||
background: var(--bs-success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.health-card .health-dot.bad {
|
.health-dot.bad {
|
||||||
background: var(--bs-danger);
|
background: #dc3545;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
/* Tables: sticky head & compact borders */
|
.table td,
|
||||||
.table thead th {
|
.table th {
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 2;
|
|
||||||
background: var(--bs-body-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table thead {
|
|
||||||
border-bottom: 1px solid var(--bs-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table> :not(caption)>*>* {
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overflow shadows for scrollable containers (JS toggles classes) */
|
/* Dividers */
|
||||||
|
.vr {
|
||||||
|
width: 1px;
|
||||||
|
min-height: 1rem;
|
||||||
|
background: rgba(255, 255, 255, .15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- horizontal scroll & nowrap for wide tables --- */
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
position: relative;
|
overflow-x: auto;
|
||||||
mask-image: linear-gradient(to right, transparent 0, black 12px, black calc(100% - 12px), transparent 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-responsive.is-scrolled-start {
|
.table-nowrap {
|
||||||
mask-image: linear-gradient(to right, black 0, black calc(100% - 12px), transparent 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-responsive.is-scrolled-end {
|
|
||||||
mask-image: linear-gradient(to right, transparent 0, black 12px, black 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pre blocks */
|
|
||||||
pre {
|
|
||||||
background: var(--bs-tertiary-bg);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: .75rem;
|
|
||||||
border: 1px solid var(--bs-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
.site-footer {
|
|
||||||
background: var(--bs-body-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons & utilities */
|
|
||||||
.btn .bi {
|
|
||||||
margin-right: .35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make first column narrow-friendly */
|
|
||||||
.w-1 {
|
|
||||||
width: 1%;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Slightly denser tabs bar and nicer corners */
|
@media (min-width: 992px) {
|
||||||
.nav-tabs .nav-link {
|
.table-nowrap-lg-normal {
|
||||||
border: 0;
|
white-space: normal;
|
||||||
border-bottom: 2px solid transparent;
|
}
|
||||||
padding: .6rem .8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs .nav-link.active {
|
/* sticky first column (for wide tables) */
|
||||||
border-bottom-color: var(--bs-primary);
|
.sticky-col {
|
||||||
font-weight: 600;
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background: var(--bs-body-bg);
|
||||||
|
box-shadow: 1px 0 0 rgba(255, 255, 255, .08);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global loading spacing */
|
footer.site-footer {
|
||||||
#global-loading {
|
border-top: 1px solid rgba(255, 255, 255, .1);
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#global-loading.show {
|
footer.site-footer a {
|
||||||
display: block;
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer.site-footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast container constraints */
|
||||||
|
#toast-container .toast {
|
||||||
|
max-width: min(420px, 90vw);
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toast-container {
|
||||||
|
max-width: 92vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#toast-container {
|
||||||
|
width: min(480px, 96vw);
|
||||||
|
max-width: min(480px, 96vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
#toast-container .toast {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-fixed.bottom-0.end-0.p-3 {
|
||||||
|
right: max(env(safe-area-inset-right), 1rem);
|
||||||
|
bottom: max(env(safe-area-inset-bottom), 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row chevron (expandable rows) */
|
||||||
|
.table .chev {
|
||||||
|
width: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tr.expandable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tr.expandable .chev::before {
|
||||||
|
content: "▸";
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform .15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tr.expanded .chev::before {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
content: "▾";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Small utility widths */
|
||||||
|
.w-1 {
|
||||||
|
width: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtle skeleton */
|
||||||
|
.skel {
|
||||||
|
position: relative;
|
||||||
|
background: linear-gradient(90deg, rgba(255, 255, 255, .05) 25%, rgba(255, 255, 255, .10) 37%, rgba(255, 255, 255, .05) 63%);
|
||||||
|
background-size: 400% 100%;
|
||||||
|
animation: skel 1.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skel {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#vm-admin,
|
||||||
|
#vm-admin .table-responsive,
|
||||||
|
#vm-admin table,
|
||||||
|
#vm-admin tbody,
|
||||||
|
#vm-admin tr,
|
||||||
|
#vm-admin td {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vm-admin td {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vm-admin .target-node {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1001;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toasts: hard-pinned to bottom-right corner */
|
/* Toasts: hard-pinned to bottom-right corner */
|
||||||
|
@@ -2,53 +2,28 @@
|
|||||||
<html lang="en" data-bs-theme="dark">
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>PVE HA Panel</title>
|
<title>PVE HA Panel</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<!-- Bootstrap & Icons -->
|
<link href="{{ url_for('static', filename='styles.css') }}" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet" />
|
|
||||||
|
|
||||||
<!-- App styles -->
|
|
||||||
<link href="{{ url_for('static', filename='styles.css') }}" rel="stylesheet" />
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!-- Top Navbar -->
|
<div class="container py-4">
|
||||||
<nav class="navbar navbar-expand-lg sticky-top border-bottom bg-body">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand fw-semibold d-flex align-items-center gap-2" href="#">
|
|
||||||
<i class="bi bi-hdd-network"></i> PVE HA Panel
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#topNav">
|
<div class="d-flex flex-wrap justify-content-between align-items-center mb-3 gap-2">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<h1 class="h4 m-0">PVE HA Panel</h1>
|
||||||
</button>
|
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||||
|
<input class="form-control form-control-sm" id="node" value="{{ node }}" style="width: 180px" aria-label="Node">
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" id="btnToggleAll" aria-expanded="false">Collapse/Expand
|
||||||
|
all</button>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="topNav">
|
<div class="vr d-none d-md-block"></div>
|
||||||
<!-- Left: quick filters -->
|
|
||||||
<form class="d-flex align-items-center gap-2 me-auto mt-3 mt-lg-0 flex-wrap" role="search">
|
|
||||||
<div class="input-group input-group-sm" style="max-width: 220px;">
|
|
||||||
<span class="input-group-text"><i class="bi bi-diagram-3"></i></span>
|
|
||||||
<input class="form-control" id="node" value="{{ node }}" placeholder="Node" aria-label="Node" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Right: global controls -->
|
<button class="btn btn-primary btn-sm" id="btnRefresh">Refresh now</button>
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 mt-3 mt-lg-0">
|
<div class="input-group input-group-sm" style="width:220px">
|
||||||
<button class="btn btn-outline-secondary btn-sm" id="btnToggleAll" aria-expanded="false">
|
<span class="input-group-text">Auto-refresh</span>
|
||||||
<i class="bi bi-arrows-collapse"></i> Collapse/Expand all
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="vr d-none d-lg-block"></div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary btn-sm" id="btnRefresh">
|
|
||||||
<i class="bi bi-arrow-clockwise"></i> Refresh now
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="input-group input-group-sm" style="width: 240px;">
|
|
||||||
<span class="input-group-text"><i class="bi bi-clock-history"></i> Auto</span>
|
|
||||||
<select class="form-select" id="selInterval" disabled>
|
<select class="form-select" id="selInterval" disabled>
|
||||||
<option value="10000">10 s</option>
|
<option value="10000">10 s</option>
|
||||||
<option value="20000">20 s</option>
|
<option value="20000">20 s</option>
|
||||||
@@ -60,76 +35,42 @@
|
|||||||
<button class="btn btn-outline-success" id="btnAuto">OFF</button>
|
<button class="btn btn-outline-success" id="btnAuto">OFF</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="vr d-none d-lg-block"></div>
|
<div class="vr d-none d-md-block"></div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-success btn-sm" id="btnEnable">
|
<button type="button" class="btn btn-success btn-sm" id="btnEnable">Enable maintenance</button>
|
||||||
<i class="bi bi-hammer"></i> Enable maintenance
|
<button type="button" class="btn btn-danger btn-sm" id="btnDisable">Disable maintenance</button>
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-danger btn-sm" id="btnDisable">
|
|
||||||
<i class="bi bi-exclamation-octagon"></i> Disable maintenance
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="vr d-none d-lg-block"></div>
|
|
||||||
|
|
||||||
<!-- UI-only toggles (localStorage) -->
|
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnDensity" title="Toggle compact density">
|
|
||||||
<i class="bi bi-distribute-vertical"></i> Density
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnTheme" title="Toggle dark / light">
|
|
||||||
<i class="bi bi-moon-stars"></i> Theme
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Global loading -->
|
<!-- Global loading -->
|
||||||
<div id="global-loading" class="container-fluid py-2">
|
<div id="global-loading" class="d-flex align-items-center gap-2 mb-3">
|
||||||
<div class="d-flex align-items-center gap-2 small text-muted">
|
|
||||||
<div class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></div>
|
<div class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></div>
|
||||||
<span>Loading data…</span>
|
<span class="small text-muted">Loading data…</span>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Health & Tabs -->
|
|
||||||
<main class="container-fluid py-3">
|
|
||||||
<!-- HEALTH -->
|
<!-- HEALTH -->
|
||||||
<section class="card border-0 shadow-sm mb-3 health-card">
|
<div class="card mb-3 border-0 shadow health-card">
|
||||||
<div class="card-body d-flex flex-wrap align-items-center gap-3">
|
<div class="card-body d-flex flex-wrap align-items-center gap-3">
|
||||||
<span class="health-dot" id="healthDot" aria-hidden="true"></span>
|
<div class="health-dot" id="healthDot" aria-hidden="true"></div>
|
||||||
<div class="me-auto">
|
<div>
|
||||||
<div class="fw-semibold fs-6" id="healthTitle">Loading…</div>
|
<div class="fw-bold" id="healthTitle">Loading…</div>
|
||||||
<div class="text-muted small" id="healthSub">—</div>
|
<div class="text-muted small" id="healthSub">—</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Small inline metrics (space efficient) -->
|
|
||||||
<div class="d-flex flex-wrap gap-3 small text-muted" id="inlineMetrics" aria-live="polite">
|
|
||||||
<!-- You can fill from your JS if you wish; purely presentational -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Main tabs -->
|
<ul class="nav nav-tabs mb-3" id="mainTabs" role="tablist">
|
||||||
<ul class="nav nav-tabs nav-fill shadow-sm rounded overflow-auto mb-3" id="mainTabs" role="tablist">
|
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-ha" type="button">
|
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-ha" type="button">HA</button>
|
||||||
<i class="bi bi-shield-lock"></i> HA
|
|
||||||
</button>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-nonha" type="button">
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-nonha" type="button">VM/CT (non-HA)</button>
|
||||||
<i class="bi bi-cpu"></i> VM/CT (non-HA)
|
|
||||||
</button>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-admin" type="button">
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-admin" type="button">VM Admin</button>
|
||||||
<i class="bi bi-tools"></i> VM Admin
|
|
||||||
</button>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-nodes" type="button">
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-nodes" type="button">Nodes</button>
|
||||||
<i class="bi bi-diagram-3"></i> Nodes
|
|
||||||
</button>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@@ -139,7 +80,7 @@
|
|||||||
<div class="accordion" id="acc">
|
<div class="accordion" id="acc">
|
||||||
|
|
||||||
<!-- Cluster / Quorum -->
|
<!-- Cluster / Quorum -->
|
||||||
<div class="accordion-item rounded-3 overflow-hidden mb-2">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="h-q">
|
<h2 class="accordion-header" id="h-q">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#c-q">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#c-q">
|
||||||
Cluster / Quorum
|
Cluster / Quorum
|
||||||
@@ -155,8 +96,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Systemd (HA) -->
|
<!-- Systemd -->
|
||||||
<div class="accordion-item rounded-3 overflow-hidden mb-2">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="h-sd">
|
<h2 class="accordion-header" id="h-sd">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#c-sd">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#c-sd">
|
||||||
Systemd (HA)
|
Systemd (HA)
|
||||||
@@ -164,15 +105,13 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div id="c-sd" class="accordion-collapse collapse" data-bs-parent="#acc">
|
<div id="c-sd" class="accordion-collapse collapse" data-bs-parent="#acc">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div id="units" class="d-flex flex-wrap gap-2">
|
<div id="units" class="d-flex flex-wrap gap-2"><span class="text-muted small">Loading…</span></div>
|
||||||
<span class="text-muted small">Loading…</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Replication -->
|
<!-- Replication -->
|
||||||
<div class="accordion-item rounded-3 overflow-hidden mb-2">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="h-repl">
|
<h2 class="accordion-header" id="h-repl">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
data-bs-target="#c-repl">
|
data-bs-target="#c-repl">
|
||||||
@@ -187,7 +126,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- HA resources -->
|
<!-- HA resources -->
|
||||||
<div class="accordion-item rounded-3 overflow-hidden mb-2">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="h-res">
|
<h2 class="accordion-header" id="h-res">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
data-bs-target="#c-res">
|
data-bs-target="#c-res">
|
||||||
@@ -197,7 +136,7 @@
|
|||||||
<div id="c-res" class="accordion-collapse collapse" data-bs-parent="#acc">
|
<div id="c-res" class="accordion-collapse collapse" data-bs-parent="#acc">
|
||||||
<div class="accordion-body table-responsive">
|
<div class="accordion-body table-responsive">
|
||||||
<table class="table table-sm table-striped align-middle table-nowrap" id="ha-res">
|
<table class="table table-sm table-striped align-middle table-nowrap" id="ha-res">
|
||||||
<thead class="position-sticky top-0 bg-body">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-1"></th>
|
<th class="w-1"></th>
|
||||||
<th>SID</th>
|
<th>SID</th>
|
||||||
@@ -209,13 +148,13 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="small text-muted">Click a row to expand VM/CT details.</div>
|
<div class="small text-muted">Click to expand VM/CT data.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Raw -->
|
<!-- Raw -->
|
||||||
<div class="accordion-item rounded-3 overflow-hidden">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="h-raw">
|
<h2 class="accordion-header" id="h-raw">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
data-bs-target="#c-raw">
|
data-bs-target="#c-raw">
|
||||||
@@ -226,10 +165,10 @@
|
|||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<pre id="pvecm" class="mb-0 small pre-scrollable"></pre>
|
<pre id="pvecm" class="mb-0 small"></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<pre id="cfgtool" class="mb-0 small pre-scrollable"></pre>
|
<pre id="cfgtool" class="mb-0 small"></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -241,10 +180,10 @@
|
|||||||
|
|
||||||
<!-- TAB: Non-HA -->
|
<!-- TAB: Non-HA -->
|
||||||
<div class="tab-pane fade" id="tab-nonha">
|
<div class="tab-pane fade" id="tab-nonha">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0">
|
||||||
<div class="card-body table-responsive">
|
<div class="card-body table-responsive">
|
||||||
<table class="table table-sm table-striped align-middle table-nowrap" id="nonha">
|
<table class="table table-sm table-striped align-middle table-nowrap" id="nonha">
|
||||||
<thead class="position-sticky top-0 bg-body">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-1"></th>
|
<th class="w-1"></th>
|
||||||
<th>SID</th>
|
<th>SID</th>
|
||||||
@@ -260,17 +199,17 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="small text-muted">Click a row to expand VM/CT details.</div>
|
<div class="small text-muted">Kliknij wiersz, aby rozwinąć szczegóły VM/CT.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TAB: VM Admin -->
|
<!-- TAB: VM Admin -->
|
||||||
<div class="tab-pane fade" id="tab-admin">
|
<div class="tab-pane fade" id="tab-admin">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0">
|
||||||
<div class="card-body table-responsive">
|
<div class="card-body table-responsive">
|
||||||
<table class="table table-sm table-striped align-middle table-nowrap" id="vm-admin">
|
<table class="table table-sm table-striped align-middle table-nowrap" id="vm-admin">
|
||||||
<thead class="position-sticky top-0 bg-body">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>SID</th>
|
<th>SID</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
@@ -293,12 +232,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TAB: Nodes -->
|
|
||||||
|
<!-- TAB: Nodes (expandable) -->
|
||||||
<div class="tab-pane fade" id="tab-nodes">
|
<div class="tab-pane fade" id="tab-nodes">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0">
|
||||||
<div class="card-body table-responsive">
|
<div class="card-body table-responsive">
|
||||||
<table class="table table-sm table-striped align-middle table-nowrap" id="nodes">
|
<table class="table table-sm table-striped align-middle table-nowrap" id="nodes">
|
||||||
<thead class="position-sticky top-0 bg-body">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-1"></th>
|
<th class="w-1"></th>
|
||||||
<th>Node</th>
|
<th>Node</th>
|
||||||
@@ -324,8 +264,8 @@
|
|||||||
|
|
||||||
<div class="text-muted small mt-3" id="footer">—</div>
|
<div class="text-muted small mt-3" id="footer">—</div>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- SITE FOOTER -->
|
||||||
<footer class="site-footer mt-4 pt-3 pb-4 text-muted small border-top">
|
<footer class="site-footer mt-4 pt-3 pb-4 text-muted small">
|
||||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||||
<div>© 2025 PVE HA Panel</div>
|
<div>© 2025 PVE HA Panel</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -336,20 +276,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</div>
|
||||||
|
|
||||||
<!-- Toasts -->
|
<!-- Toasts -->
|
||||||
<div class="toast-fixed-anchor">
|
<div aria-live="polite" aria-atomic="true" class="position-fixed bottom-0 end-0" style="z-index: 1080">
|
||||||
<div id="toast-container" class="toast-container"></div>
|
<div id="toast-container" class="toast-container"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<!-- UI-only enhancements (safe; no API changes) -->
|
|
||||||
<script type="module" src="{{ url_for('static', filename='js/ui.js') }}"></script>
|
|
||||||
|
|
||||||
<!-- Your logic (unchanged ids/contracts) -->
|
|
||||||
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
|
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user