UX Phase 1 follow-ups: state v2 + reset, window defaults + support, palette support; analysis JSON generation; tests for LIMIT/metadata; README updates
This commit is contained in:
parent
fab91d2e04
commit
6de85b7cc5
5 changed files with 193 additions and 16 deletions
|
@ -74,6 +74,9 @@
|
|||
<input type="checkbox" id="exclude-uncached-toggle" checked> Exclude “-”
|
||||
</label>
|
||||
</div>
|
||||
<div id="reset-control" class="control">
|
||||
<button id="reset-view" class="button is-small is-light">Reset view</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="recent-section">
|
||||
|
@ -122,6 +125,7 @@
|
|||
groupOthers,
|
||||
movingAverage,
|
||||
} from './chartManager.js';
|
||||
const STATE_KEY = 'ngxstat-state-v2';
|
||||
const intervalSelect = document.getElementById('interval-select');
|
||||
const domainSelect = document.getElementById('domain-select');
|
||||
const intervalControl = document.getElementById('interval-control');
|
||||
|
@ -131,6 +135,7 @@
|
|||
const modeGroupControl = document.getElementById('mode-group-control');
|
||||
const excludeUncachedControl = document.getElementById('exclude-uncached-control');
|
||||
const smoothControl = document.getElementById('smooth-control');
|
||||
const resetButton = document.getElementById('reset-view');
|
||||
const tabs = document.querySelectorAll('#report-tabs li');
|
||||
const sections = {
|
||||
recent: document.getElementById('recent-section'),
|
||||
|
@ -172,10 +177,11 @@
|
|||
let modeGroup = true;
|
||||
let excludeUncached = true;
|
||||
let smoothError = false;
|
||||
let hadExplicitWindow = false; // URL or saved-state provided window
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
localStorage.setItem('ngxstat-state', JSON.stringify({
|
||||
localStorage.setItem(STATE_KEY, JSON.stringify({
|
||||
tab: currentTab,
|
||||
interval: currentInterval,
|
||||
domain: currentDomain,
|
||||
|
@ -190,11 +196,11 @@
|
|||
|
||||
function loadSavedState() {
|
||||
try {
|
||||
const s = JSON.parse(localStorage.getItem('ngxstat-state') || '{}');
|
||||
const s = JSON.parse(localStorage.getItem(STATE_KEY) || '{}');
|
||||
if (s.tab) currentTab = s.tab;
|
||||
if (s.interval) currentInterval = s.interval;
|
||||
if (s.domain !== undefined) currentDomain = s.domain;
|
||||
if (s.window) currentWindow = s.window;
|
||||
if (s.window) { currentWindow = s.window; hadExplicitWindow = true; }
|
||||
if (s.percent !== undefined) modePercent = !!Number(s.percent);
|
||||
if (s.group !== undefined) modeGroup = !!Number(s.group);
|
||||
if (s.exclude_dash !== undefined) excludeUncached = !!Number(s.exclude_dash);
|
||||
|
@ -207,7 +213,7 @@
|
|||
if (params.get('tab')) currentTab = params.get('tab');
|
||||
if (params.get('interval')) currentInterval = params.get('interval');
|
||||
if (params.get('domain') !== null) currentDomain = params.get('domain') || '';
|
||||
if (params.get('window')) currentWindow = params.get('window');
|
||||
if (params.get('window')) { currentWindow = params.get('window'); hadExplicitWindow = true; }
|
||||
if (params.get('percent') !== null) modePercent = params.get('percent') === '1';
|
||||
if (params.get('group') !== null) modeGroup = params.get('group') === '1';
|
||||
if (params.get('exclude_dash') !== null) excludeUncached = params.get('exclude_dash') === '1';
|
||||
|
@ -273,8 +279,13 @@
|
|||
}
|
||||
// Windowing for time series
|
||||
if (isTimeSeries) {
|
||||
const n = bucketsForWindow(currentWindow, currentInterval);
|
||||
transformed = sliceWindow(transformed, n);
|
||||
// Only apply windowing if report supports current window (if constrained)
|
||||
const supported = Array.isArray(rep.windows_supported) ? rep.windows_supported : null;
|
||||
const canWindow = !supported || supported.includes(currentWindow);
|
||||
if (canWindow) {
|
||||
const n = bucketsForWindow(currentWindow, currentInterval);
|
||||
transformed = sliceWindow(transformed, n);
|
||||
}
|
||||
}
|
||||
// Distributions: percent + group small
|
||||
const isDistribution = ['pie', 'polarArea', 'doughnut', 'donut'].includes(rep.chart);
|
||||
|
@ -306,7 +317,7 @@
|
|||
options.scales.y.stacked = true;
|
||||
// Build multiple series from columns (exclude bucket & total)
|
||||
const keys = transformed.length ? Object.keys(transformed[0]).filter(k => k !== bucketField && k !== 'total') : [];
|
||||
const palette = rep.colors || [
|
||||
const palette = rep.colors || rep.palette || [
|
||||
'#3273dc', '#23d160', '#ffdd57', '#ff3860', '#7957d5', '#363636'
|
||||
];
|
||||
datasets = keys.map((k, i) => ({
|
||||
|
@ -327,6 +338,9 @@
|
|||
if (rep.colors) {
|
||||
dataset.backgroundColor = rep.colors;
|
||||
dataset.borderColor = rep.colors;
|
||||
} else if (rep.palette) {
|
||||
dataset.backgroundColor = rep.palette;
|
||||
dataset.borderColor = rep.palette;
|
||||
} else if (rep.color) {
|
||||
dataset.backgroundColor = rep.color;
|
||||
dataset.borderColor = rep.color;
|
||||
|
@ -392,6 +406,15 @@
|
|||
if (currentTab === 'tables') return rep.chart === 'table';
|
||||
return true;
|
||||
});
|
||||
// If no explicit window was given (URL or saved state), honor first report's default
|
||||
if (!hadExplicitWindow) {
|
||||
const withDefault = filtered.find(r => r.window_default);
|
||||
if (withDefault && typeof withDefault.window_default === 'string') {
|
||||
currentWindow = withDefault.window_default;
|
||||
windowSelect.value = currentWindow;
|
||||
updateURL();
|
||||
}
|
||||
}
|
||||
filtered.forEach(rep => {
|
||||
fetch(path + '/' + rep.html, { signal: token.controller.signal })
|
||||
.then(r => r.text())
|
||||
|
@ -499,10 +522,12 @@
|
|||
intervalControl.classList.toggle('is-hidden', !showInterval);
|
||||
domainControl.classList.toggle('is-hidden', !showDomain);
|
||||
windowControl.classList.toggle('is-hidden', !showInterval);
|
||||
modePercentControl.classList.toggle('is-hidden', !showInterval);
|
||||
modeGroupControl.classList.toggle('is-hidden', !showInterval);
|
||||
excludeUncachedControl.classList.toggle('is-hidden', !showInterval);
|
||||
smoothControl.classList.toggle('is-hidden', !showInterval);
|
||||
// Only show percent/group/exclude toggles on Distribution tab,
|
||||
// and smoothing only on Trends tab
|
||||
modePercentControl.classList.toggle('is-hidden', name !== 'distribution');
|
||||
modeGroupControl.classList.toggle('is-hidden', name !== 'distribution');
|
||||
excludeUncachedControl.classList.toggle('is-hidden', name !== 'distribution');
|
||||
smoothControl.classList.toggle('is-hidden', name !== 'trends');
|
||||
updateURL();
|
||||
if (name === 'recent') {
|
||||
loadStats();
|
||||
|
@ -570,6 +595,23 @@
|
|||
switchTab(tab.dataset.tab);
|
||||
});
|
||||
});
|
||||
resetButton.addEventListener('click', () => {
|
||||
try {
|
||||
localStorage.removeItem('ngxstat-state'); // clear legacy
|
||||
localStorage.removeItem(STATE_KEY);
|
||||
} catch {}
|
||||
// Reset to hard defaults
|
||||
currentTab = 'recent';
|
||||
currentInterval = intervalSelect.value = intervalSelect.options[0]?.value || currentInterval;
|
||||
currentDomain = domainSelect.value = '';
|
||||
currentWindow = windowSelect.value = '7d';
|
||||
modePercent = percentToggle.checked = false;
|
||||
modeGroup = groupToggle.checked = true;
|
||||
excludeUncached = excludeUncachedToggle.checked = true;
|
||||
smoothError = smoothToggle.checked = false;
|
||||
hadExplicitWindow = false;
|
||||
switchTab(currentTab);
|
||||
});
|
||||
// Initialize state (URL -> localStorage -> defaults)
|
||||
loadSavedState();
|
||||
applyURLParams();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue