Update dashboard template

main
Marcin Cieślikowski 3 weeks ago
parent a17b7aad9d
commit 88cbba01b6

@ -1,8 +1,9 @@
<script src="assets/vendor/tailwindcss-3.4.17-cdn.js"></script> <script src="assets/vendor/tailwindcss-3.4.17-cdn.js"></script>
<link rel="stylesheet" href="assets/vendor/fontawesome/css/all.min.css"> <link rel="stylesheet" href="assets/vendor/fontawesome/css/all.min.css">
<script> <script>
tailwind.config = { tailwind.config = {
darkMode: 'class', darkMode: 'class',
important: '#nscDashboardRoot',
theme: { theme: {
extend: { extend: {
colors: { colors: {
@ -23,34 +24,153 @@
} }
</script> </script>
<style> <style>
/* Wider, modern macOS-style scrollbar for popovers */ #nscDashboardRoot {
.custom-scrollbar::-webkit-scrollbar { all: initial;
display: block;
position: relative;
width: 100%;
min-height: 100%;
padding: 1rem;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 16px;
color: #1e293b;
line-height: 1.5;
text-align: initial;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#nscDashboardRoot .nsc-dashboard-bg {
position: absolute;
inset: 0;
z-index: 0;
min-height: 100%;
background-color: #f8fafc;
pointer-events: none;
}
.dark #nscDashboardRoot .nsc-dashboard-bg {
background-color: #0f172a;
}
#nscDashboardRoot .nsc-dashboard-content {
position: relative;
z-index: 1;
width: 100%;
max-width: 1920px;
margin-inline: auto;
}
.dark #nscDashboardRoot {
color: #e2e8f0;
}
#nscDashboardRoot,
#nscDashboardRoot *,
#nscDashboardRoot *::before,
#nscDashboardRoot *::after {
box-sizing: border-box;
}
#nscDashboardRoot button,
#nscDashboardRoot input {
font: inherit;
}
#nscDashboardRoot :where(h1, h2, h3, h4, h5, p, button, input) {
margin: 0;
}
#nscDashboardRoot :where(h1, h2, h3, h4, h5, p) {
padding: 0;
border: 0;
background: transparent;
font-style: normal;
}
#nscDashboardRoot :where(button, input) {
color: inherit;
}
#nscDashboardRoot button {
background: transparent;
cursor: pointer;
}
#nscDashboardRoot input {
box-sizing: border-box !important;
margin: 0 !important;
}
#nscDashboardRoot button {
box-sizing: border-box !important;
margin: 0 !important;
font-size: inherit !important;
line-height: inherit !important;
}
#nscDashboardRoot button {
min-width: 0 !important;
min-height: 0 !important;
border-width: 0;
}
#nscDashboardRoot button.text-xs {
font-size: 0.75rem !important;
line-height: 1rem !important;
}
#nscDashboardRoot button.text-sm {
font-size: 0.875rem !important;
line-height: 1.25rem !important;
}
#nscDashboardRoot button.px-2 {
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}
#nscDashboardRoot button.px-3 {
padding-left: 0.75rem !important;
padding-right: 0.75rem !important;
}
#nscDashboardRoot button.px-4 {
padding-left: 1rem !important;
padding-right: 1rem !important;
}
#nscDashboardRoot button.px-5 {
padding-left: 1.25rem !important;
padding-right: 1.25rem !important;
}
#nscDashboardRoot button.py-1 {
padding-top: 0.25rem !important;
padding-bottom: 0.25rem !important;
}
#nscDashboardRoot button.py-1\.5 {
padding-top: 0.375rem !important;
padding-bottom: 0.375rem !important;
}
#nscDashboardRoot button.py-2 {
padding-top: 0.5rem !important;
padding-bottom: 0.5rem !important;
}
#nscDashboardRoot button.py-2\.5 {
padding-top: 0.625rem !important;
padding-bottom: 0.625rem !important;
}
/* Wider, modern macOS-style scrollbar for popovers */
#nscDashboardRoot .custom-scrollbar::-webkit-scrollbar {
width: 12px; width: 12px;
} }
.custom-scrollbar::-webkit-scrollbar-track { #nscDashboardRoot .custom-scrollbar::-webkit-scrollbar-track {
background: transparent; background: transparent;
border-radius: 8px; border-radius: 8px;
} }
.custom-scrollbar::-webkit-scrollbar-thumb { #nscDashboardRoot .custom-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(156, 163, 175, 0.5); /* Semi-transparent gray */ background-color: rgba(156, 163, 175, 0.5); /* Semi-transparent gray */
border: 3px solid transparent; /* Inner border */ border: 3px solid transparent; /* Inner border */
background-clip: padding-box; background-clip: padding-box;
border-radius: 8px; border-radius: 8px;
} }
.dark .custom-scrollbar::-webkit-scrollbar-thumb { .dark #nscDashboardRoot .custom-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(107, 114, 128, 0.6); background-color: rgba(107, 114, 128, 0.6);
} }
.custom-scrollbar::-webkit-scrollbar-thumb:hover { #nscDashboardRoot .custom-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(107, 114, 128, 0.8); background-color: rgba(107, 114, 128, 0.8);
border-width: 2px; border-width: 2px;
} }
</style> </style>
<div id="nscDashboardRoot">
<div class="nsc-dashboard-bg" aria-hidden="true"></div>
<div class="nsc-dashboard-content">
<div id="nscTerminalDashboard" class="flex flex-col gap-6"> <div id="nscTerminalDashboard" class="flex flex-col gap-6">
<!-- The dashboard UI is injected here by JavaScript --> <!-- The dashboard UI is injected here by JavaScript -->
</div> </div>
<!-- Custom confirm modal replacing window.confirm --> <!-- Custom confirm modal replacing window.confirm -->
<div id="customConfirmModal" class="fixed inset-0 z-[999] flex items-center justify-center hidden opacity-0 transition-opacity duration-300"> <div id="customConfirmModal" class="fixed inset-0 z-[999] flex items-center justify-center hidden opacity-0 transition-opacity duration-300">
<div class="absolute inset-0 bg-slate-900/50 backdrop-blur-sm" id="customConfirmOverlay"></div> <div class="absolute inset-0 bg-slate-900/50 backdrop-blur-sm" id="customConfirmOverlay"></div>
@ -74,6 +194,8 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<script> <script>
// Initialize dark mode // Initialize dark mode
@ -273,6 +395,16 @@
<i class="fa fa-info-circle"></i> Inicjalizacja... <i class="fa fa-info-circle"></i> Inicjalizacja...
</div> </div>
<div id="nscCommandProgress" class="hidden bg-white dark:bg-slate-800 rounded-xl p-4 shadow-soft border border-slate-200 dark:border-slate-700">
<div class="flex items-center justify-between gap-4 text-xs font-bold mb-2">
<span id="nscCommandProgressLabel" class="text-slate-600 dark:text-slate-300">Wysyłanie poleceń...</span>
<span id="nscCommandProgressCount" class="text-slate-900 dark:text-white">0 / 0</span>
</div>
<div class="h-2.5 w-full bg-slate-100 dark:bg-slate-700 rounded-full overflow-hidden">
<div id="nscCommandProgressBar" class="h-full bg-brand-500 transition-all duration-300" style="width: 0%"></div>
</div>
</div>
<!-- Global summary view --> <!-- Global summary view -->
<div id="nscExhibitionCard"></div> <div id="nscExhibitionCard"></div>
@ -288,6 +420,26 @@
el.innerHTML = `<i class="fa ${isError ? 'fa-exclamation-triangle' : 'fa-check-circle'}"></i> ${msg}`; el.innerHTML = `<i class="fa ${isError ? 'fa-exclamation-triangle' : 'fa-check-circle'}"></i> ${msg}`;
} }
function setCommandProgress(sent, total, label) {
const progress = document.getElementById("nscCommandProgress");
const progressBar = document.getElementById("nscCommandProgressBar");
const progressCount = document.getElementById("nscCommandProgressCount");
const progressLabel = document.getElementById("nscCommandProgressLabel");
if (!progress || !progressBar || !progressCount || !progressLabel) return;
const pct = total ? Math.round((sent / total) * 100) : 0;
progress.classList.remove("hidden");
progressLabel.textContent = label;
progressCount.textContent = `${sent} / ${total}`;
progressBar.style.width = `${pct}%`;
}
function hideCommandProgress(delayMs = 1200) {
const progress = document.getElementById("nscCommandProgress");
if (!progress) return;
setTimeout(() => progress.classList.add("hidden"), delayMs);
}
function openPopover(locId) { function openPopover(locId) {
document.querySelectorAll('[id^="popover-"]').forEach(p => closePopover(p.id.replace('popover-', ''))); document.querySelectorAll('[id^="popover-"]').forEach(p => closePopover(p.id.replace('popover-', '')));
state.openPopoverId = locId; state.openPopoverId = locId;
@ -416,7 +568,7 @@
const on = loc.terminals.filter(isTerminalOn).length; const on = loc.terminals.filter(isTerminalOn).length;
const pct = total ? Math.round((on/total)*100) : 0; const pct = total ? Math.round((on/total)*100) : 0;
const pcs = loc.terminals.filter(t => t.player === 'PC'); const pcs = loc.terminals.filter(t => t.player === 'UVP');
const onPcs = pcs.filter(isTerminalOn).length; const onPcs = pcs.filter(isTerminalOn).length;
const lights = loc.terminals.filter(t => t.player === 'CTRL'); const lights = loc.terminals.filter(t => t.player === 'CTRL');
@ -591,7 +743,7 @@
async function executeCommand(command, locationId, type) { async function executeCommand(command, locationId, type) {
const targets = state.terminals.filter(t => { const targets = state.terminals.filter(t => {
if(locationId !== 'all' && t.location_id !== locationId) return false; if(locationId !== 'all' && t.location_id !== locationId) return false;
if(type !== 'all' && (type === 'computer' ? t.player !== 'PC' : t.player !== 'CTRL')) return false; if(type !== 'all' && (type === 'computer' ? t.player !== 'UVP' : t.player !== 'CTRL')) return false;
return true; return true;
}); });
if(!targets.length) return setStatus("Brak urządzeń spełniających kryteria.", true); if(!targets.length) return setStatus("Brak urządzeń spełniających kryteria.", true);
@ -601,12 +753,14 @@
async function executeCommandBulk(command, targets, scopeLabel) { async function executeCommandBulk(command, targets, scopeLabel) {
state.busy = true; state.busy = true;
setStatus(`Wysyłanie polecenia ${command} do ${targets.length} urządzeń (${scopeLabel})...`, false); setStatus(`Wysyłanie polecenia ${command} do ${targets.length} urządzeń (${scopeLabel})...`, false);
setCommandProgress(0, targets.length, `Wysłano polecenie do 0 z ${targets.length} urządzeń`);
try { try {
// Preview mode skips FETCH requests // Preview mode skips FETCH requests
const isPreview = window.location.protocol === 'about:' || window.location.protocol === 'blob:' || window.location.hostname === ''; const isPreview = window.location.protocol === 'about:' || window.location.protocol === 'blob:' || window.location.hostname === '';
let failedCount = 0; let failedCount = 0;
let sentCount = 0;
for(const t of targets) { for(const t of targets) {
if (isPreview) { if (isPreview) {
@ -618,18 +772,21 @@
try { try {
const body = new URLSearchParams(); const body = new URLSearchParams();
body.set("command", command); body.set("command", command);
body.set("tag_id", t.id); const url = `${API_COMMAND}&terminal_id=${encodeURIComponent(t.id)}`;
const res = await fetch(API_COMMAND, { method: "POST", body: body }); const res = await fetch(url, { method: "POST", body: body });
if(!res.ok) failedCount++; if(!res.ok) failedCount++;
} catch(e) { } catch(e) {
failedCount++; failedCount++;
} }
} }
sentCount++;
setCommandProgress(sentCount, targets.length, `Wysłano polecenie do ${sentCount} z ${targets.length} urządzeń`);
} }
if(failedCount > 0) setStatus(`Zakończono z błędami. Niepowodzenia: ${failedCount}/${targets.length}`, true); if(failedCount > 0) setStatus(`Zakończono z błędami. Niepowodzenia: ${failedCount}/${targets.length}`, true);
else setStatus(`Polecenie wykonane pomyślnie dla ${targets.length} urządzeń.`, false); else setStatus(`Polecenie wykonane pomyślnie dla ${targets.length} urządzeń.`, false);
hideCommandProgress();
setTimeout(() => refreshTerminals(true), 1500); setTimeout(() => refreshTerminals(true), 1500);
} finally { } finally {

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save