Merge pull request #27 from wagesj45/codex/implement-tabbed-interface-for-reports

Add Bulma tabs to dashboard
This commit is contained in:
Jordan Wages 2025-07-19 00:26:59 -05:00 committed by GitHub
commit 5de2aa4c66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -9,8 +9,17 @@
<body class="section"> <body class="section">
<div class="container"> <div class="container">
<h1 class="title">ngxstat Reports</h1> <h1 class="title">ngxstat Reports</h1>
<div class="field is-grouped">
<div class="control has-icons-left"> <div class="tabs is-toggle" id="report-tabs">
<ul>
<li class="is-active" data-tab="overview"><a>Overview</a></li>
<li data-tab="all"><a>All Domains</a></li>
<li data-tab="domain"><a>Per Domain</a></li>
</ul>
</div>
<div id="controls" class="field is-grouped mb-4">
<div id="interval-control" class="control has-icons-left is-hidden">
<div class="select is-small"> <div class="select is-small">
<select id="interval-select"> <select id="interval-select">
{% for interval in intervals %} {% for interval in intervals %}
@ -20,7 +29,7 @@
</div> </div>
<span class="icon is-small is-left"><i data-feather="clock"></i></span> <span class="icon is-small is-left"><i data-feather="clock"></i></span>
</div> </div>
<div class="control has-icons-left"> <div id="domain-control" class="control has-icons-left is-hidden">
<div class="select is-small"> <div class="select is-small">
<select id="domain-select"> <select id="domain-select">
<option value="">All Domains</option> <option value="">All Domains</option>
@ -32,13 +41,24 @@
<span class="icon is-small is-left"><i data-feather="server"></i></span> <span class="icon is-small is-left"><i data-feather="server"></i></span>
</div> </div>
</div> </div>
<div id="overview-section">
<div id="overview" class="box mb-5"> <div id="overview" class="box mb-5">
<h2 class="subtitle">Overview</h2> <h2 class="subtitle">Overview</h2>
<p>Total logs: <span id="stat-total">-</span></p> <p>Total logs: <span id="stat-total">-</span></p>
<p>Date range: <span id="stat-start">-</span> to <span id="stat-end">-</span></p> <p>Date range: <span id="stat-start">-</span> to <span id="stat-end">-</span></p>
<p>Unique domains: <span id="stat-domains">-</span></p> <p>Unique domains: <span id="stat-domains">-</span></p>
</div> </div>
<div id="reports-container"></div> <div id="overview-reports"></div>
</div>
<div id="all-section" class="is-hidden">
<div id="reports-all"></div>
</div>
<div id="domain-section" class="is-hidden">
<div id="reports-domain"></div>
</div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@ -47,7 +67,19 @@
<script> <script>
const intervalSelect = document.getElementById('interval-select'); const intervalSelect = document.getElementById('interval-select');
const domainSelect = document.getElementById('domain-select'); const domainSelect = document.getElementById('domain-select');
const container = document.getElementById('reports-container'); const intervalControl = document.getElementById('interval-control');
const domainControl = document.getElementById('domain-control');
const tabs = document.querySelectorAll('#report-tabs li');
const sections = {
overview: document.getElementById('overview-section'),
all: document.getElementById('all-section'),
domain: document.getElementById('domain-section')
};
const containers = {
overview: document.getElementById('overview-reports'),
all: document.getElementById('reports-all'),
domain: document.getElementById('reports-domain')
};
const totalElem = document.getElementById('stat-total'); const totalElem = document.getElementById('stat-total');
const startElem = document.getElementById('stat-start'); const startElem = document.getElementById('stat-start');
const endElem = document.getElementById('stat-end'); const endElem = document.getElementById('stat-end');
@ -55,6 +87,7 @@
let currentInterval = intervalSelect.value; let currentInterval = intervalSelect.value;
let currentDomain = domainSelect.value; let currentDomain = domainSelect.value;
let currentTab = 'overview';
function initReport(rep, base) { function initReport(rep, base) {
fetch(base + '/' + rep.json) fetch(base + '/' + rep.json)
@ -119,10 +152,23 @@
} }
function loadReports() { function loadReports() {
let path = currentInterval; let path;
if (currentDomain) { let container;
if (currentTab === 'overview') {
path = 'global';
container = containers.overview;
} else if (currentTab === 'all') {
path = currentInterval;
container = containers.all;
} else {
container = containers.domain;
if (!currentDomain) {
container.innerHTML = '<p>Select a domain</p>';
return;
}
path = 'domains/' + currentDomain + '/' + currentInterval; path = 'domains/' + currentDomain + '/' + currentInterval;
} }
fetch(path + '/reports.json') fetch(path + '/reports.json')
.then(r => r.json()) .then(r => r.json())
.then(reports => { .then(reports => {
@ -135,9 +181,26 @@
initReport(rep, path); initReport(rep, path);
}); });
}); });
feather.replace();
}); });
} }
function switchTab(name) {
currentTab = name;
tabs.forEach(tab => {
tab.classList.toggle('is-active', tab.dataset.tab === name);
});
Object.entries(sections).forEach(([key, section]) => {
section.classList.toggle('is-hidden', key !== name);
});
intervalControl.classList.toggle('is-hidden', name === 'overview');
domainControl.classList.toggle('is-hidden', name !== 'domain');
if (name === 'overview') {
loadStats();
}
loadReports();
}
intervalSelect.addEventListener('change', () => { intervalSelect.addEventListener('change', () => {
currentInterval = intervalSelect.value; currentInterval = intervalSelect.value;
loadReports(); loadReports();
@ -148,8 +211,11 @@
loadReports(); loadReports();
}); });
loadReports(); tabs.forEach(tab => {
loadStats(); tab.addEventListener('click', () => switchTab(tab.dataset.tab));
});
switchTab('overview');
feather.replace(); feather.replace();
</script> </script>
</body> </body>