diff --git a/front/js/common.js b/front/js/common.js index 135b0b36d..b6d763c7e 100755 --- a/front/js/common.js +++ b/front/js/common.js @@ -91,7 +91,17 @@ function deleteCookie (cookie) { // ----------------------------------------------------------------------------- // DateTime utilities // ----------------------------------------------------------------------------- -function localizeTimestamp(input) { +/** + * Parses a timestamp in any of several common formats (unix seconds/ms, + * DD/MM/YYYY, MM/DD/YYYY, ISO, RFC2822, DD-MM-YYYY) and formats it in the + * user's configured TIMEZONE/UI_LOCALE settings. + * @param {string|number} input - The timestamp to localize. + * @param {Intl.DateTimeFormatOptions} [dtfOptions] - Full Intl.DateTimeFormat + * options object, replacing (not merging with) the default year/month/day/ + * hour/minute/second/hour12 shape - e.g. for a shorter chart-axis label. + * @returns {string} The localized, formatted timestamp. + */ +function localizeTimestamp(input, dtfOptions) { // Read fresh on every call (not a module-level const): getSetting() reads // a localStorage cache that clearCache() wipes before reloading, so a @@ -99,6 +109,16 @@ function localizeTimestamp(input) { const tz = getSetting("TIMEZONE") || 'Europe/Berlin'; const LOCALE = getSetting('UI_LOCALE') || 'en-GB'; + // Optional second arg: full Intl.DateTimeFormat options object, replacing + // (not merging with) the historical year+seconds default below - e.g. a + // chart axis label that wants month/day/hour/minute only. Omit it for the + // original full-precision behavior every existing caller relies on. + const options = dtfOptions || { + year: 'numeric', month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit', second: '2-digit', + hour12: false + }; + input = String(input || '').trim(); // 1. Unix timestamps (10 or 13 digits) @@ -106,9 +126,7 @@ function localizeTimestamp(input) { const ms = input.length === 10 ? parseInt(input, 10) * 1000 : parseInt(input, 10); return new Intl.DateTimeFormat('default', { timeZone: tz, - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', - hour12: false + ...options }).format(new Date(ms)); } @@ -198,9 +216,7 @@ function localizeTimestamp(input) { return new Intl.DateTimeFormat(LOCALE, { // Convert from UTC to user's configured timezone timeZone: tz, - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', - hour12: false + ...options }).format(date); } } diff --git a/front/js/graph_resource_history.js b/front/js/graph_resource_history.js index 41874f8c8..f5cbe17c3 100644 --- a/front/js/graph_resource_history.js +++ b/front/js/graph_resource_history.js @@ -2,6 +2,23 @@ // building a new one on the same canvas (Chart.js throws otherwise). var resourceHistoryChartInstances = {}; +// Chart axis labels: time only - date/year are dropped entirely to keep the +// axis readable; the full date (including year) is still available on +// hover via each chart's tooltip title callback below. Passed as +// localizeTimestamp()'s options override so parsing/timezone/locale logic +// stays in one shared place (common.js). +var CHART_TIMESTAMP_OPTIONS_AXIS = { + hour: '2-digit', minute: '2-digit', + hour12: false +}; + +// Full timestamp (including year) shown in the tooltip title on hover. +var CHART_TIMESTAMP_OPTIONS_TOOLTIP = { + year: 'numeric', month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit', second: '2-digit', + hour12: false +}; + /** * Fetches Resource_History data for the given range ('hour'/'day'/'week'/'month') * and renders the four Performance-tab charts, or shows the disabled-state @@ -21,6 +38,7 @@ function initResourceHistoryGraphs(range) { $('#resourceHistoryCharts').removeClass('myhidden'); var labels = []; + var fullLabels = []; var cpuData = []; var rssData = []; var ioReadData = []; @@ -36,7 +54,8 @@ function initResourceHistoryGraphs(range) { // hour/day rows carry resDateTime (raw); week/month rows carry bucket // (hourly rollup) instead - see const.py's sql_resource_history_* queries. var ts = entry.resDateTime || entry.bucket; - labels.push(localizeTimestamp(ts).slice(0, 16)); + labels.push(localizeTimestamp(ts, CHART_TIMESTAMP_OPTIONS_AXIS)); + fullLabels.push(localizeTimestamp(ts, CHART_TIMESTAMP_OPTIONS_TOOLTIP)); cpuData.push(round2(entry.resCpuPercent)); rssData.push(round2(entry.resRssMb)); // Bytes -> MB and ms -> s: raw units from the DB aren't a readable axis scale. @@ -46,7 +65,7 @@ function initResourceHistoryGraphs(range) { tickFailed.push(entry.resTickFailed == 1); }); - renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWriteData, durationData, tickFailed); + renderResourceHistoryCharts(labels, fullLabels, cpuData, rssData, ioReadData, ioWriteData, durationData, tickFailed); }).fail(function () { console.error('Error fetching resource history data.'); }); @@ -57,9 +76,11 @@ function initResourceHistoryGraphs(range) { * memory usage MB, IO read+write MB, scan duration seconds) on the same time * axis. Rows written from the tick-failure `finally` path (resTickFailed = 1) * are rendered as visually distinct points rather than plain data, since - * their numbers may reflect a truncated, crash-adjacent sample. + * their numbers may reflect a truncated, crash-adjacent sample. `labels` + * (time-only, used for the axis) and `fullLabels` (full date+year, used for + * the hover tooltip title) are parallel arrays over the same rows. */ -function renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWriteData, durationData, tickFailed) { +function renderResourceHistoryCharts(labels, fullLabels, cpuData, rssData, ioReadData, ioWriteData, durationData, tickFailed) { var normalColor = "rgba(0, 166, 89, .8)"; var failedColor = "#dd4b39"; @@ -75,15 +96,30 @@ function renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWri var commonScales = { yAxes: [{ - ticks: { beginAtZero: true, fontColor: '#A0A0A0' }, - gridLines: { color: "rgba(0, 0, 0, 0)" }, + // maxTicksLimit caps how many labels Chart.js draws - without it, a + // short/fixed-height chart crams in enough ticks that adjacent labels + // visually overlap. + ticks: { beginAtZero: true, fontColor: '#A0A0A0', maxTicksLimit: 5 }, + // Faint gridlines only at the (few, capped) main ticks - easier to + // read a value off the chart without making it visually noisy. + gridLines: { color: "rgba(160, 160, 160, 0.15)", zeroLineColor: "rgba(160, 160, 160, 0.3)" }, }], xAxes: [{ - ticks: { fontColor: '#A0A0A0' }, - gridLines: { color: "rgba(0, 0, 0, 0)" }, + ticks: { fontColor: '#A0A0A0', maxTicksLimit: 10, autoSkip: true }, + gridLines: { color: "rgba(160, 160, 160, 0.15)", zeroLineColor: "rgba(160, 160, 160, 0.3)" }, }], }; + // Axis labels are time-only (CHART_TIMESTAMP_OPTIONS_AXIS); show the full + // date (including year) on hover instead, via fullLabels. + var commonTooltips = { + callbacks: { + title: function (tooltipItems) { + return fullLabels[tooltipItems[0].index]; + }, + }, + }; + destroyIfExists('cpu'); resourceHistoryChartInstances.cpu = new Chart("ResourceCpuChart", { type: "line", @@ -98,7 +134,7 @@ function renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWri fill: true, }], }, - options: { legend: { display: true }, scales: commonScales, maintainAspectRatio: false, responsive: true }, + options: { legend: { display: true }, scales: commonScales, tooltips: commonTooltips, maintainAspectRatio: false, responsive: true }, }); destroyIfExists('rss'); @@ -115,7 +151,7 @@ function renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWri fill: true, }], }, - options: { legend: { display: true }, scales: commonScales, maintainAspectRatio: false, responsive: true }, + options: { legend: { display: true }, scales: commonScales, tooltips: commonTooltips, maintainAspectRatio: false, responsive: true }, }); destroyIfExists('io'); @@ -140,7 +176,7 @@ function renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWri }, ], }, - options: { legend: { display: true }, scales: commonScales, maintainAspectRatio: false, responsive: true }, + options: { legend: { display: true }, scales: commonScales, tooltips: commonTooltips, maintainAspectRatio: false, responsive: true }, }); destroyIfExists('duration'); @@ -157,6 +193,6 @@ function renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWri fill: true, }], }, - options: { legend: { display: true }, scales: commonScales, maintainAspectRatio: false, responsive: true }, + options: { legend: { display: true }, scales: commonScales, tooltips: commonTooltips, maintainAspectRatio: false, responsive: true }, }); } diff --git a/front/systeminfoPerformance.php b/front/systeminfoPerformance.php index c07a85067..fd1af16be 100644 --- a/front/systeminfoPerformance.php +++ b/front/systeminfoPerformance.php @@ -11,7 +11,7 @@

- +

@@ -28,17 +28,17 @@
-
+
-
+
-
+

-
+