From 0e8c64c64ccf9c718a934e12cdb420475768a639 Mon Sep 17 00:00:00 2001 From: jx163 Date: Thu, 28 Aug 2025 19:12:34 +1200 Subject: [PATCH 1/4] Add support for glances uptime --- src/components/Widgets/GlancesUptime.vue | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/components/Widgets/GlancesUptime.vue diff --git a/src/components/Widgets/GlancesUptime.vue b/src/components/Widgets/GlancesUptime.vue new file mode 100644 index 0000000000..be99edf7f7 --- /dev/null +++ b/src/components/Widgets/GlancesUptime.vue @@ -0,0 +1,62 @@ + + + + + From 3b55d8bc9d1cbb028b6ee7f05fc5dc2a8b4e6550 Mon Sep 17 00:00:00 2001 From: jx163 Date: Fri, 29 Aug 2025 11:30:41 +1200 Subject: [PATCH 2/4] Add glances uptime in WidgetBase.vue --- src/components/Widgets/WidgetBase.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index f67c72aa61..9222d9a665 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -123,6 +123,7 @@ const COMPAT = { weather: 'Weather', 'weather-forecast': 'WeatherForecast', 'xkcd-comic': 'XkcdComic', + 'glances-uptime': 'GlancesUptime', }; export default { From 2c2fd5430c6058c747b43ad07c460871e299f714 Mon Sep 17 00:00:00 2001 From: jx163 Date: Sat, 13 Sep 2025 10:50:20 +1200 Subject: [PATCH 3/4] Show uptime and start time --- src/components/Widgets/GlancesUptime.vue | 109 ++++++++++++++++++++--- 1 file changed, 97 insertions(+), 12 deletions(-) diff --git a/src/components/Widgets/GlancesUptime.vue b/src/components/Widgets/GlancesUptime.vue index be99edf7f7..83e7017fdf 100644 --- a/src/components/Widgets/GlancesUptime.vue +++ b/src/components/Widgets/GlancesUptime.vue @@ -1,45 +1,130 @@ From 21d5b421f9c1fa22f94e117c884059e07d49c408 Mon Sep 17 00:00:00 2001 From: jx163 Date: Wed, 17 Sep 2025 14:28:24 +1200 Subject: [PATCH 4/4] Make glances uptime more readable --- src/components/Widgets/GlancesUptime.vue | 352 +++++++++++++++++++++-- 1 file changed, 334 insertions(+), 18 deletions(-) diff --git a/src/components/Widgets/GlancesUptime.vue b/src/components/Widgets/GlancesUptime.vue index 83e7017fdf..054e518b08 100644 --- a/src/components/Widgets/GlancesUptime.vue +++ b/src/components/Widgets/GlancesUptime.vue @@ -1,5 +1,38 @@ @@ -17,18 +75,80 @@ import WidgetMixin from '@/mixins/WidgetMixin'; import GlancesMixin from '@/mixins/GlancesMixin'; const UptimeUtils = { - format(seconds) { + format(seconds, formatType = 'auto') { if (!seconds) return ''; + const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; - const hh = String(hours).padStart(2, '0'); - const mm = String(minutes).padStart(2, '0'); - const ss = String(secs).padStart(2, '0'); + switch (formatType) { + case 'days': + return `${Math.floor(seconds / 86400)} days`; - return days > 0 ? `${days} days, ${hh}:${mm}:${ss}` : `${hh}:${mm}:${ss}`; + case 'hours': + return `${Math.floor(seconds / 3600)} hours`; + + case 'minutes': + return `${Math.floor(seconds / 60)} minutes`; + + case 'seconds': + return `${seconds} seconds`; + + case 'dhm': + if (days > 0) { + return `${days} days, ${hours} hours, ${minutes} minutes`; + } else if (hours > 0) { + return `${hours} hours, ${minutes} minutes`; + } else { + return `${minutes} minutes`; + } + + case 'hms': { + const totalHours = Math.floor(seconds / 3600); + const mm = String(minutes).padStart(2, '0'); + const ss = String(secs).padStart(2, '0'); + return totalHours > 0 ? `${totalHours}:${mm}:${ss}` : `${mm}:${ss}`; + } + + case 'compact': + if (days > 0) { + return `${days}d ${String(hours).padStart(2, '0')}h ${String(minutes).padStart(2, '0')}m ${String(secs).padStart(2, '0')}s`; + } else { + return `${String(hours).padStart(2, '0')}h ${String(minutes).padStart(2, '0')}m ${String(secs).padStart(2, '0')}s`; + } + + case 'full': { + const hhFull = String(hours).padStart(2, '0'); + const mmFull = String(minutes).padStart(2, '0'); + const ssFull = String(secs).padStart(2, '0'); + return days > 0 + ? `${days} days, ${hhFull}:${mmFull}:${ssFull}` + : `${hhFull}:${mmFull}:${ssFull}`; + } + + case 'auto': + default: + if (days >= 30) { + return `${days} days (${Math.floor(days / 30)} months)`; + } else if (days >= 7) { + return `${days} days (${Math.floor(days / 7)} weeks)`; + } else if (days > 0) { + const hhAuto = String(hours).padStart(2, '0'); + const mmAuto = String(minutes).padStart(2, '0'); + const ssAuto = String(secs).padStart(2, '0'); + return `${days} days, ${hhAuto}:${mmAuto}:${ssAuto}`; + } else if (hours > 0) { + const mmAuto = String(minutes).padStart(2, '0'); + const ssAuto = String(secs).padStart(2, '0'); + return `${hours}:${mmAuto}:${ssAuto}`; + } else if (minutes > 0) { + return `${minutes}:${String(secs).padStart(2, '0')}`; + } else { + return `${secs} seconds`; + } + } }, parse(str) { @@ -37,9 +157,9 @@ const UptimeUtils = { if (!match) return null; const [, d, h, m, s] = match; return (parseInt(d || 0, 10) * 86400) - + (parseInt(h, 10) * 3600) - + (parseInt(m, 10) * 60) - + parseInt(s, 10); + + (parseInt(h, 10) * 3600) + + (parseInt(m, 10) * 60) + + parseInt(s, 10); }, }; @@ -52,6 +172,11 @@ export default { startTime: null, refreshTimer: null, realtimeTimer: null, + selectedFormat: 'auto', + showDetails: false, + cpuData: null, + memData: null, + fsData: null, }; }, computed: { @@ -59,9 +184,38 @@ export default { return this.makeGlancesUrl('uptime'); }, formattedUptime() { - return UptimeUtils.format(this.secondsSinceBoot); + return UptimeUtils.format(this.secondsSinceBoot, this.selectedFormat); + }, + }, + watch: { + selectedFormat(newFormat) { + try { + localStorage.setItem('glances-uptime-format', newFormat); + } catch (e) { + console.warn('Failed to save uptime format preference:', e); + } }, }, + mounted() { + try { + const savedFormat = localStorage.getItem('glances-uptime-format'); + if (savedFormat) { + this.selectedFormat = savedFormat; + } + } catch (e) { + console.warn('Failed to load uptime format preference:', e); + } + + this.fetchUptime(); + this.fetchDetails(); + this.refreshTimer = setInterval(() => { + this.fetchUptime(); + this.fetchDetails(); + }, 5000); + }, + beforeUnmount() { + this.clearTimer('all'); + }, methods: { async fetchUptime() { try { @@ -72,6 +226,22 @@ export default { } }, + async fetchDetails() { + try { + const [cpuResp, memResp, fsResp] = await Promise.all([ + axios.get(this.makeGlancesUrl('cpu')), + axios.get(this.makeGlancesUrl('mem')), + axios.get(this.makeGlancesUrl('fs')), + ]); + + this.cpuData = cpuResp.data; + this.memData = memResp.data; + this.fsData = fsResp.data; + } catch (e) { + console.warn('Failed to fetch details:', e); + } + }, + processData(resp) { let seconds = 0; @@ -118,29 +288,175 @@ export default { } }, }, - mounted() { - this.fetchUptime(); - this.refreshTimer = setInterval(this.fetchUptime, 5000); - }, - beforeUnmount() { - this.clearTimer('all'); - }, };