2025-05-21 18:35:55 +02:00
|
|
|
<script setup>
|
|
|
|
|
|
|
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
|
const i18n = useI18n();
|
|
|
|
|
const t = i18n.t;
|
|
|
|
|
|
2025-07-03 12:33:02 +02:00
|
|
|
import { ref, onMounted, onUnmounted, useTemplateRef, nextTick } from 'vue';
|
|
|
|
|
import { SingleSelect } from 'pankow';
|
2025-05-21 18:35:55 +02:00
|
|
|
import Section from './Section.vue';
|
|
|
|
|
import SystemModel from '../models/SystemModel.js';
|
2025-07-01 22:32:59 +02:00
|
|
|
import { prettyDecimalSize } from 'pankow/utils';
|
2025-07-03 12:33:02 +02:00
|
|
|
import GraphItem from './GraphItem.vue';
|
2025-05-21 18:35:55 +02:00
|
|
|
|
|
|
|
|
const systemModel = SystemModel.create();
|
|
|
|
|
|
|
|
|
|
const periods = [
|
2025-07-03 17:31:49 +02:00
|
|
|
{ hours: 0, label: t('app.graphs.period.live'), format: 'hh:mm A', tooltipFormat: 'hh:mm:ss A' },
|
2025-07-03 11:30:31 +02:00
|
|
|
{ hours: 1, label: t('app.graphs.period.1h'), format: 'hh:mm A', tooltipFormat: 'hh:mm A' },
|
|
|
|
|
{ hours: 6, label: t('app.graphs.period.6h'), format: 'hh:mm A', tooltipFormat: 'hh:mm A' },
|
|
|
|
|
{ hours: 12, label: t('app.graphs.period.12h'), format: 'hh:mm A', tooltipFormat: 'hh:mm A' },
|
|
|
|
|
{ hours: 24, label: t('app.graphs.period.24h'), format: 'hh:mm A', tooltipFormat: 'hh:mm A' },
|
|
|
|
|
{ hours: 24*7, label: t('app.graphs.period.7d'), format: 'DD MMM', tooltipFormat: 'DD MMM hh:mm A' },
|
|
|
|
|
{ hours: 24*30, label: t('app.graphs.period.30d'), format: 'DD MMM', tooltipFormat: 'DD MMM hh:mm A' },
|
2025-05-21 18:35:55 +02:00
|
|
|
];
|
|
|
|
|
|
2025-07-03 12:33:02 +02:00
|
|
|
const busy = ref(true);
|
2025-07-03 11:30:31 +02:00
|
|
|
const period = ref(periods[0]);
|
2025-07-03 12:33:02 +02:00
|
|
|
const cpuGraphItem = useTemplateRef('cpuGraphItem');
|
|
|
|
|
const memoryGraphItem = useTemplateRef('memoryGraphItem');
|
|
|
|
|
const diskGraphItem = useTemplateRef('diskGraphItem');
|
|
|
|
|
const networkGraphItem = useTemplateRef('networkGraphItem');
|
2025-07-01 22:32:59 +02:00
|
|
|
|
|
|
|
|
const networkReadTotal = ref(0);
|
|
|
|
|
const networkWriteTotal = ref(0);
|
|
|
|
|
|
|
|
|
|
const blockReadTotal = ref(0);
|
|
|
|
|
const blockWriteTotal = ref(0);
|
2025-05-21 18:35:55 +02:00
|
|
|
|
|
|
|
|
let systemMemory = {};
|
2025-05-22 18:45:05 +02:00
|
|
|
let systemCpus = {};
|
2025-05-21 18:35:55 +02:00
|
|
|
let metricStream = null;
|
|
|
|
|
|
2025-05-23 11:40:25 +02:00
|
|
|
const LIVE_REFRESH_INTERVAL_MSECS = 500;
|
2025-07-03 10:03:14 +02:00
|
|
|
|
2025-05-21 18:35:55 +02:00
|
|
|
async function liveRefresh() {
|
2025-05-23 11:40:25 +02:00
|
|
|
metricStream = await systemModel.getMetricStream(LIVE_REFRESH_INTERVAL_MSECS);
|
2025-05-21 18:35:55 +02:00
|
|
|
metricStream.onerror = (error) => console.log('event stream error:', error);
|
|
|
|
|
metricStream.onmessage = (message) => {
|
|
|
|
|
const data = JSON.parse(message.data);
|
|
|
|
|
|
2025-07-03 12:33:02 +02:00
|
|
|
cpuGraphItem.value.pushData(data.cpu);
|
|
|
|
|
memoryGraphItem.value.pushData(data.memory, data.swap);
|
|
|
|
|
diskGraphItem.value.pushData(data.blockReadRate, data.blockWriteRate);
|
|
|
|
|
networkGraphItem.value.pushData(data.networkReadRate, data.networkWriteRate);
|
2025-07-01 22:32:59 +02:00
|
|
|
|
|
|
|
|
blockReadTotal.value = prettyDecimalSize(data.blockReadTotal);
|
|
|
|
|
blockWriteTotal.value = prettyDecimalSize(data.blockWriteTotal);
|
|
|
|
|
networkReadTotal.value = prettyDecimalSize(data.networkReadTotal);
|
|
|
|
|
networkWriteTotal.value = prettyDecimalSize(data.networkWriteTotal);
|
2025-05-21 18:35:55 +02:00
|
|
|
};
|
2025-07-01 22:15:13 +02:00
|
|
|
}
|
2025-05-21 18:35:55 +02:00
|
|
|
|
2025-07-01 22:32:59 +02:00
|
|
|
async function onPeriodChange() {
|
2025-07-03 12:33:02 +02:00
|
|
|
if (metricStream) {
|
|
|
|
|
metricStream.close();
|
|
|
|
|
metricStream = null;
|
2025-07-01 22:32:59 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-03 12:33:02 +02:00
|
|
|
if (period.value.hours === 0) return await liveRefresh();
|
2025-07-01 22:32:59 +02:00
|
|
|
|
2025-07-03 12:33:02 +02:00
|
|
|
const [error, metrics] = await systemModel.getMetrics({ fromSecs: period.value.hours * 60 * 60, intervalSecs: 300 });
|
|
|
|
|
if (error) return console.error(error);
|
2025-07-01 22:32:59 +02:00
|
|
|
|
2025-07-03 12:33:02 +02:00
|
|
|
cpuGraphItem.value.setData(metrics.cpu);
|
|
|
|
|
memoryGraphItem.value.setData(metrics.memory, metrics.swap);
|
|
|
|
|
diskGraphItem.value.setData(metrics.blockReadRate, metrics.blockWriteRate);
|
|
|
|
|
networkGraphItem.value.setData(metrics.networkReadRate, metrics.networkWriteRate);
|
2025-07-01 22:32:59 +02:00
|
|
|
|
|
|
|
|
networkReadTotal.value = prettyDecimalSize(metrics.networkReadTotal);
|
|
|
|
|
networkWriteTotal.value = prettyDecimalSize(metrics.networkWriteTotal);
|
|
|
|
|
blockReadTotal.value = prettyDecimalSize(metrics.blockReadTotal);
|
|
|
|
|
blockWriteTotal.value = prettyDecimalSize(metrics.blockWriteTotal);
|
2025-05-21 18:35:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
2025-05-22 18:45:05 +02:00
|
|
|
let error, result;
|
|
|
|
|
[error, result] = await systemModel.memory();
|
2025-05-21 18:35:55 +02:00
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
systemMemory = result;
|
|
|
|
|
|
2025-05-22 18:45:05 +02:00
|
|
|
[error, result] = await systemModel.cpus();
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
systemCpus = result;
|
|
|
|
|
|
2025-07-03 12:33:02 +02:00
|
|
|
busy.value = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2025-07-01 22:32:59 +02:00
|
|
|
await onPeriodChange();
|
2025-05-21 18:35:55 +02:00
|
|
|
});
|
|
|
|
|
|
2025-05-22 12:26:30 +02:00
|
|
|
onUnmounted(async () => {
|
2025-07-03 12:33:02 +02:00
|
|
|
if (metricStream) metricStream.close();
|
2025-05-22 12:26:30 +02:00
|
|
|
});
|
|
|
|
|
|
2025-05-21 18:35:55 +02:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<Section :title="$t('system.graphs.title')">
|
|
|
|
|
<template #header-buttons>
|
2025-07-03 11:30:31 +02:00
|
|
|
<SingleSelect @select="onPeriodChange()" v-model="period" :options="periods" option-label="label"/>
|
2025-05-21 18:35:55 +02:00
|
|
|
</template>
|
2025-05-22 19:51:41 +02:00
|
|
|
|
2025-07-03 12:33:02 +02:00
|
|
|
<div class="graphs" v-if="!busy">
|
|
|
|
|
<GraphItem ref="cpuGraphItem"
|
|
|
|
|
:title="$t('system.cpuUsage.title')"
|
|
|
|
|
:subtext='systemCpus.length ? `${systemCpus.length} Core "${systemCpus[0].model}"` : ""'
|
|
|
|
|
:period="period"
|
|
|
|
|
yscale="cpu"
|
|
|
|
|
:dataset-labels="['CPU']"
|
2025-07-03 15:59:31 +02:00
|
|
|
:dataset-colors="['#9ad0f5']"
|
2025-07-03 12:33:02 +02:00
|
|
|
:cpu="systemCpus"
|
|
|
|
|
>
|
|
|
|
|
</GraphItem>
|
|
|
|
|
|
|
|
|
|
<GraphItem ref="memoryGraphItem"
|
|
|
|
|
:title="$t('system.systemMemory.title')"
|
|
|
|
|
:subtext="`RAM: ${prettyDecimalSize(systemMemory.memory)} Swap: ${prettyDecimalSize(systemMemory.swap)}`"
|
|
|
|
|
:period="period"
|
|
|
|
|
yscale="memory"
|
|
|
|
|
:dataset-labels="['Memory', 'Swap']"
|
2025-07-03 15:59:31 +02:00
|
|
|
:dataset-colors="['#9ad0f5', '#ffb1c1']"
|
2025-07-03 12:33:02 +02:00
|
|
|
:memory="systemMemory"
|
|
|
|
|
>
|
|
|
|
|
</GraphItem>
|
|
|
|
|
|
|
|
|
|
<GraphItem ref="diskGraphItem"
|
|
|
|
|
title="Disk I/O"
|
|
|
|
|
:subtext="$t('app.graphs.diskIOTotal', { read: blockReadTotal, write: blockWriteTotal })"
|
|
|
|
|
:period="period"
|
|
|
|
|
yscale="disk"
|
|
|
|
|
:dataset-labels="['Read', 'Write']"
|
2025-07-03 15:59:31 +02:00
|
|
|
:dataset-colors="['#9ad0f5', '#ffb1c1']"
|
2025-07-03 12:33:02 +02:00
|
|
|
>
|
|
|
|
|
</GraphItem>
|
|
|
|
|
|
|
|
|
|
<GraphItem ref="networkGraphItem"
|
|
|
|
|
title="Network I/O"
|
|
|
|
|
:subtext="$t('app.graphs.networkIOTotal', { inbound: networkReadTotal, outbound: networkWriteTotal })"
|
|
|
|
|
:period="period"
|
|
|
|
|
yscale="network"
|
|
|
|
|
:dataset-labels="['RX', 'TX']"
|
2025-07-03 15:59:31 +02:00
|
|
|
:dataset-colors="['#9ad0f5', '#ffb1c1']"
|
2025-07-03 12:33:02 +02:00
|
|
|
>
|
|
|
|
|
</GraphItem>
|
2025-05-21 18:35:55 +02:00
|
|
|
</div>
|
|
|
|
|
</Section>
|
|
|
|
|
</template>
|