172 lines
6.3 KiB
Vue
172 lines
6.3 KiB
Vue
<script setup>
|
|
|
|
import { useI18n } from 'vue-i18n';
|
|
const i18n = useI18n();
|
|
const t = i18n.t;
|
|
|
|
import { ref, onMounted, useTemplateRef, nextTick, onUnmounted } from 'vue';
|
|
import AppsModel from '../../models/AppsModel.js';
|
|
import { prettyBinarySize, prettyDecimalSize } from '@cloudron/pankow/utils';
|
|
import SystemModel from '../../models/SystemModel.js';
|
|
import { SingleSelect } from '@cloudron/pankow';
|
|
import GraphItem from '../GraphItem.vue';
|
|
|
|
const { app } = defineProps([ 'app' ]);
|
|
|
|
const appsModel = AppsModel.create();
|
|
const systemModel = SystemModel.create();
|
|
|
|
const periods = [
|
|
{ hours: 0, label: t('app.graphs.period.live'), tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm:ss A' },
|
|
{ hours: 1, intervalSecs: 20, label: t('app.graphs.period.1h'), tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm:ss A' }, // 180 points
|
|
{ hours: 6, intervalSecs: 60, label: t('app.graphs.period.6h'), tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm A' },
|
|
{ hours: 12, intervalSecs: 240, label: t('app.graphs.period.12h'), tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm A' },
|
|
{ hours: 24, intervalSecs: 480, label: t('app.graphs.period.24h'), tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm A' },
|
|
{ hours: 24*7, intervalSecs: 960, label: t('app.graphs.period.7d'), tickFormat: 'DD MMM', tooltipFormat: 'DD MMM hh:mm A' },
|
|
{ hours: 24*30, intervalSecs: 960, label: t('app.graphs.period.30d'), tickFormat: 'DD MMM', tooltipFormat: 'DD MMM hh:mm A' }, // 2700 points
|
|
];
|
|
|
|
const busy = ref(true);
|
|
const period = ref(periods[0]);
|
|
const cpuGraphItem = useTemplateRef('cpuGraphItem');
|
|
const memoryGraphItem = useTemplateRef('memoryGraphItem');
|
|
const diskGraphItem = useTemplateRef('diskGraphItem');
|
|
const networkGraphItem = useTemplateRef('networkGraphItem');
|
|
|
|
const networkReadTotal = ref(0);
|
|
const networkWriteTotal = ref(0);
|
|
|
|
const blockReadTotal = ref(0);
|
|
const blockWriteTotal = ref(0);
|
|
|
|
const appMemory = app.memoryLimit || app.manifest.memoryLimit || 256*1024*1024;
|
|
let systemCpus = {};
|
|
let metricStream = null;
|
|
|
|
function createDatasets() {
|
|
const datasets = {
|
|
cpu: [{ label: 'CPU', color: '#9ad0f5', stack: 'cpu', data: [] }],
|
|
memory: [{ label: 'Memory', color: '#9ad0f5', stack: 'memory', data: [] }],
|
|
disk: [
|
|
{ label: 'Read', color: '#9ad0f5', stack: 'read', data: [] },
|
|
{ label: 'Write', color: '#ffb1c1', stack: 'write', data: [] }
|
|
],
|
|
network: [
|
|
{ label: 'RX', color: '#9ad0f5', stack: 'rx', data: [] },
|
|
{ label: 'TX', color: '#ffb1c1', stack: 'tx', data: [] }
|
|
],
|
|
};
|
|
return datasets;
|
|
}
|
|
|
|
async function liveRefresh() {
|
|
metricStream = await appsModel.getMetricStream(app.id);
|
|
metricStream.onerror = (error) => console.log('event stream error:', error);
|
|
metricStream.onmessage = (message) => {
|
|
const data = JSON.parse(message.data);
|
|
|
|
cpuGraphItem.value.pushData(0, data[app.id].cpu);
|
|
memoryGraphItem.value.pushData(0, data[app.id].memory);
|
|
diskGraphItem.value.pushData(0, data[app.id].blockReadRate, data[app.id].blockWriteRate);
|
|
networkGraphItem.value.pushData(0, data[app.id].networkReadRate, data[app.id].networkWriteRate);
|
|
|
|
blockReadTotal.value = prettyDecimalSize(data[app.id].blockReadTotal);
|
|
blockWriteTotal.value = prettyDecimalSize(data[app.id].blockWriteTotal);
|
|
networkReadTotal.value = prettyDecimalSize(data[app.id].networkReadTotal);
|
|
networkWriteTotal.value = prettyDecimalSize(data[app.id].networkWriteTotal);
|
|
};
|
|
}
|
|
|
|
async function rebuild() {
|
|
if (metricStream) {
|
|
metricStream.close();
|
|
metricStream = null;
|
|
}
|
|
|
|
const datasets = createDatasets();
|
|
|
|
if (period.value.hours !== 0) {
|
|
const [error, metrics] = await appsModel.getMetrics(app.id, { fromSecs: period.value.hours * 60 * 60, intervalSecs: period.value.intervalSecs });
|
|
if (error) return console.error(error);
|
|
|
|
datasets.cpu[0].data = metrics[app.id].cpu;
|
|
datasets.memory[0].data = metrics[app.id].memory;
|
|
datasets.disk[0].data = metrics[app.id].blockReadRate;
|
|
datasets.disk[1].data = metrics[app.id].blockWriteRate;
|
|
datasets.network[0].data = metrics[app.id].networkReadRate;
|
|
datasets.network[1].data = metrics[app.id].networkWriteRate;
|
|
|
|
networkReadTotal.value = prettyDecimalSize(metrics.networkReadTotal);
|
|
networkWriteTotal.value = prettyDecimalSize(metrics.networkWriteTotal);
|
|
blockReadTotal.value = prettyDecimalSize(metrics.blockReadTotal);
|
|
blockWriteTotal.value = prettyDecimalSize(metrics.blockWriteTotal);
|
|
}
|
|
|
|
cpuGraphItem.value.setDatasets(datasets.cpu);
|
|
memoryGraphItem.value.setDatasets(datasets.memory);
|
|
diskGraphItem.value.setDatasets(datasets.disk);
|
|
networkGraphItem.value.setDatasets(datasets.network);
|
|
|
|
if (period.value.hours === 0) return await liveRefresh();
|
|
}
|
|
|
|
onMounted(async () => {
|
|
const [error, result] = await systemModel.cpus();
|
|
if (error) return console.error(error);
|
|
|
|
systemCpus = result;
|
|
|
|
busy.value = false;
|
|
await nextTick();
|
|
|
|
await rebuild();
|
|
});
|
|
|
|
onUnmounted(async () => {
|
|
if (metricStream) metricStream.close();
|
|
});
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="graphs" v-if="!busy">
|
|
<div style="text-align: right"><SingleSelect @select="rebuild()" v-model="period" :options="periods" option-label="label"/></div>
|
|
|
|
<GraphItem ref="cpuGraphItem"
|
|
:title="$t('system.cpuUsage.title')"
|
|
:subtext="`${app.cpuQuota}% of ${systemCpus.length} Core ${systemCpus[0].model}`"
|
|
:period="period"
|
|
yscale="cpu"
|
|
:cpu-count="systemCpus.length"
|
|
:high-mark="systemCpus.length * app.cpuQuota/100"
|
|
>
|
|
</GraphItem>
|
|
|
|
<GraphItem ref="memoryGraphItem"
|
|
title="Memory"
|
|
:subtext="`RAM: ${prettyBinarySize(app.memoryLimit || app.manifest.memoryLimit || 256*1024*1024)}`"
|
|
:period="period"
|
|
yscale="memory"
|
|
:memory="appMemory"
|
|
:high-mark="appMemory"
|
|
>
|
|
</GraphItem>
|
|
|
|
<GraphItem ref="diskGraphItem"
|
|
title="Disk I/O"
|
|
:subtext="$t('app.graphs.diskIOTotal', { read: blockReadTotal, write: blockWriteTotal })"
|
|
:period="period"
|
|
yscale="disk"
|
|
>
|
|
</GraphItem>
|
|
|
|
<GraphItem ref="networkGraphItem"
|
|
title="Network I/O"
|
|
:subtext="$t('app.graphs.networkIOTotal', { inbound: networkReadTotal, outbound: networkWriteTotal })"
|
|
:period="period"
|
|
yscale="network"
|
|
>
|
|
</GraphItem>
|
|
</div>
|
|
</template>
|