mirror of
https://github.com/nicolargo/glances.git
synced 2026-05-24 22:46:28 -04:00
First version of the new WebUI
This commit is contained in:
@@ -1,48 +1 @@
|
||||
// Custom.scss
|
||||
|
||||
// Option A: Include all of Bootstrap
|
||||
// ==================================
|
||||
|
||||
// Include any default variable overrides here (though functions won't be available)
|
||||
|
||||
// @import "../node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
// Then add additional custom code here
|
||||
|
||||
|
||||
|
||||
// // Option B: Include parts of Bootstrap
|
||||
// // ====================================
|
||||
|
||||
// // 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
|
||||
@import "../node_modules/bootstrap/scss/functions";
|
||||
|
||||
// // 2. Include any default variable overrides here
|
||||
// $body-bg: black;
|
||||
|
||||
// // 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
|
||||
@import "../node_modules/bootstrap/scss/variables";
|
||||
@import "../node_modules/bootstrap/scss/variables-dark";
|
||||
|
||||
// // 4. Include any default map overrides here
|
||||
|
||||
// // 5. Include remainder of required parts
|
||||
@import "../node_modules/bootstrap/scss/maps";
|
||||
@import "../node_modules/bootstrap/scss/mixins";
|
||||
@import "../node_modules/bootstrap/scss/root";
|
||||
|
||||
// // 6. Optionally include any other parts as needed
|
||||
@import "../node_modules/bootstrap/scss/utilities";
|
||||
@import "../node_modules/bootstrap/scss/reboot";
|
||||
@import "../node_modules/bootstrap/scss/type";
|
||||
@import "../node_modules/bootstrap/scss/images";
|
||||
@import "../node_modules/bootstrap/scss/containers";
|
||||
@import "../node_modules/bootstrap/scss/grid";
|
||||
@import "../node_modules/bootstrap/scss/helpers";
|
||||
@import "../node_modules/bootstrap/scss/tables";
|
||||
@import "../node_modules/bootstrap/scss/progress";
|
||||
|
||||
// // 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss`
|
||||
@import "../node_modules/bootstrap/scss/utilities/api";
|
||||
|
||||
// // 8. Add additional custom code here
|
||||
// No Bootstrap - custom theme only
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,219 +1,86 @@
|
||||
<template>
|
||||
<div v-if="!dataLoaded" id="loading-page" class="container-fluid">
|
||||
<div class="loader">Glances is loading...</div>
|
||||
<div v-if="!dataLoaded" class="loading-page">
|
||||
Glances is loading...
|
||||
</div>
|
||||
<glances-help v-else-if="args.help_tag"></glances-help>
|
||||
<main v-else>
|
||||
<!-- Display minimal header on low screen size (smarthphone) -->
|
||||
<div class="d-sm-none">
|
||||
<div class="header-small">
|
||||
<div v-if="!args.disable_system"><glances-plugin-hostname :data="data"></glances-plugin-hostname></div>
|
||||
<div v-if="!args.disable_uptime"><glances-plugin-uptime :data="data"></glances-plugin-uptime></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Display standard header on others screen sizes -->
|
||||
<div class="d-none d-sm-block">
|
||||
<div class="header d-flex justify-content-between flex-row">
|
||||
<div v-if="!args.disable_system" class=""><glances-plugin-system :data="data"></glances-plugin-system>
|
||||
</div>
|
||||
<div v-if="!args.disable_ip" class="d-none d-lg-block"><glances-plugin-ip
|
||||
:data="data"></glances-plugin-ip>
|
||||
</div>
|
||||
<div v-if="!args.disable_uptime" class="d-none d-md-block"><glances-plugin-uptime
|
||||
:data="data"></glances-plugin-uptime></div>
|
||||
<div v-if="!args.disable_now" class="d-none d-xl-block"><glances-plugin-now
|
||||
:data="data"></glances-plugin-now></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex d-none d-sm-block">
|
||||
<div v-if="!args.disable_cloud">
|
||||
<glances-plugin-cloud :data="data"></glances-plugin-cloud>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Display top menu with CPU, MEM, LOAD...-->
|
||||
<div class="top d-flex justify-content-between flex-row">
|
||||
<!-- Quicklook -->
|
||||
<div v-if="!args.disable_quicklook" class="d-none d-md-block">
|
||||
<glances-plugin-quicklook :data="data"></glances-plugin-quicklook>
|
||||
</div>
|
||||
<!-- CPU -->
|
||||
<div v-if="!args.disable_cpu || !args.percpu" class="">
|
||||
<glances-plugin-cpu :data="data"></glances-plugin-cpu>
|
||||
</div>
|
||||
<!-- TODO: percpu need to be refactor
|
||||
<div class="col"
|
||||
v-if="!args.disable_cpu && !args.percpu">
|
||||
<glances-plugin-cpu :data="data"></glances-plugin-cpu>
|
||||
</div>
|
||||
<div class="col"
|
||||
v-if="!args.disable_cpu && args.percpu">
|
||||
<glances-plugin-percpu :data="data"></glances-plugin-percpu>
|
||||
</div> -->
|
||||
<template v-else>
|
||||
<!-- HEADER -->
|
||||
<header-section :data="data"></header-section>
|
||||
|
||||
<!-- NPU -->
|
||||
<div v-if="!args.disable_npu && hasNpu" class="d-none d-xl-block">
|
||||
<glances-plugin-npu :data="data"></glances-plugin-npu>
|
||||
</div>
|
||||
<!-- GPU -->
|
||||
<div v-if="!args.disable_gpu && hasGpu" class="d-none d-xl-block">
|
||||
<glances-plugin-gpu :data="data"></glances-plugin-gpu>
|
||||
</div>
|
||||
<!-- MEM -->
|
||||
<div v-if="!args.disable_mem" class="">
|
||||
<glances-plugin-mem :data="data"></glances-plugin-mem>
|
||||
</div>
|
||||
<!-- SWAP -->
|
||||
<div v-if="!args.disable_memswap" class="d-none d-lg-block">
|
||||
<glances-plugin-memswap :data="data"></glances-plugin-memswap>
|
||||
</div>
|
||||
<!-- LOAD -->
|
||||
<div v-if="!args.disable_load" class="d-none d-sm-block">
|
||||
<glances-plugin-load :data="data"></glances-plugin-load>
|
||||
</div>
|
||||
<!-- METRICS STRIP -->
|
||||
<section id="metrics" :class="metricsClass">
|
||||
<metrics-cpu v-if="!args.disable_cpu" :data="data"></metrics-cpu>
|
||||
<metrics-mem v-if="!args.disable_mem" :data="data"></metrics-mem>
|
||||
<metrics-load v-if="!args.disable_load" :data="data"></metrics-load>
|
||||
<metrics-gpu v-if="!args.disable_gpu && hasGpu" :data="data"></metrics-gpu>
|
||||
</section>
|
||||
|
||||
<!-- BODY: sidebar + main -->
|
||||
<div id="body" :class="{ 'no-sidebar': args.disable_left_sidebar }">
|
||||
<sidebar-section v-if="!args.disable_left_sidebar" :data="data"></sidebar-section>
|
||||
<main-process :data="data"></main-process>
|
||||
</div>
|
||||
<!-- Display bottom of the screen with sidebar and processlist -->
|
||||
<div class="bottom container-fluid">
|
||||
<div class="row">
|
||||
<div v-if="!args.disable_left_sidebar" class="col-3 d-none d-md-block"
|
||||
:class="{ 'sidebar-min': !args.percpu, 'sidebar-max': args.percpu }">
|
||||
<template v-for="plugin in leftMenu">
|
||||
<component :is="`glances-plugin-${plugin}`" v-if="!args[`disable_${plugin}`]" :id="`${plugin}`"
|
||||
:data="data">
|
||||
</component>
|
||||
</template>
|
||||
</div>
|
||||
<div class="col" :class="{ 'sidebar-min': !args.percpu, 'sidebar-max': args.percpu }">
|
||||
<glances-plugin-vms v-if="!args.disable_vms" :data="data"></glances-plugin-vms>
|
||||
<glances-plugin-containers v-if="!args.disable_containers" :data="data"></glances-plugin-containers>
|
||||
<glances-plugin-process :data="data"></glances-plugin-process>
|
||||
<glances-plugin-alert v-if="!args.disable_alert" :data="data"></glances-plugin-alert>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- FOOTER ALERTS -->
|
||||
<footer-alerts v-if="!args.disable_alert" :data="data"></footer-alerts>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import hotkeys from "hotkeys-js";
|
||||
import GlancesHelp from "./components/help.vue";
|
||||
import GlancesPluginAlert from "./components/plugin-alert.vue";
|
||||
import GlancesPluginCloud from "./components/plugin-cloud.vue";
|
||||
import GlancesPluginConnections from "./components/plugin-connections.vue";
|
||||
import GlancesPluginContainers from "./components/plugin-containers.vue";
|
||||
import GlancesPluginCpu from "./components/plugin-cpu.vue";
|
||||
import GlancesPluginDiskio from "./components/plugin-diskio.vue";
|
||||
import GlancesPluginFolders from "./components/plugin-folders.vue";
|
||||
import GlancesPluginFs from "./components/plugin-fs.vue";
|
||||
import GlancesPluginNpu from "./components/plugin-npu.vue";
|
||||
import GlancesPluginGpu from "./components/plugin-gpu.vue";
|
||||
import GlancesPluginHostname from "./components/plugin-hostname.vue";
|
||||
import GlancesPluginIp from "./components/plugin-ip.vue";
|
||||
import GlancesPluginIrq from "./components/plugin-irq.vue";
|
||||
import GlancesPluginLoad from "./components/plugin-load.vue";
|
||||
import GlancesPluginMem from "./components/plugin-mem.vue";
|
||||
import GlancesPluginMemswap from "./components/plugin-memswap.vue";
|
||||
import GlancesPluginNetwork from "./components/plugin-network.vue";
|
||||
import GlancesPluginNow from "./components/plugin-now.vue";
|
||||
import GlancesPluginPercpu from "./components/plugin-percpu.vue";
|
||||
import GlancesPluginPorts from "./components/plugin-ports.vue";
|
||||
import GlancesPluginProcess from "./components/plugin-process.vue";
|
||||
import GlancesPluginQuicklook from "./components/plugin-quicklook.vue";
|
||||
import GlancesPluginRaid from "./components/plugin-raid.vue";
|
||||
import GlancesPluginSensors from "./components/plugin-sensors.vue";
|
||||
import GlancesPluginSmart from "./components/plugin-smart.vue";
|
||||
import GlancesPluginSystem from "./components/plugin-system.vue";
|
||||
import GlancesPluginUptime from "./components/plugin-uptime.vue";
|
||||
import GlancesPluginVms from "./components/plugin-vms.vue";
|
||||
import GlancesPluginWifi from "./components/plugin-wifi.vue";
|
||||
import HeaderSection from "./components/header-section.vue";
|
||||
import MetricsCpu from "./components/metrics-cpu.vue";
|
||||
import MetricsMem from "./components/metrics-mem.vue";
|
||||
import MetricsLoad from "./components/metrics-load.vue";
|
||||
import MetricsGpu from "./components/metrics-gpu.vue";
|
||||
import SidebarSection from "./components/sidebar-section.vue";
|
||||
import MainProcess from "./components/main-process.vue";
|
||||
import FooterAlerts from "./components/footer-alerts.vue";
|
||||
import { GlancesStats } from "./services.js";
|
||||
import { store } from "./store.js";
|
||||
|
||||
import uiconfig from "./uiconfig.json";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlancesHelp,
|
||||
GlancesPluginAlert,
|
||||
GlancesPluginCloud,
|
||||
GlancesPluginConnections,
|
||||
GlancesPluginCpu,
|
||||
GlancesPluginDiskio,
|
||||
GlancesPluginContainers,
|
||||
GlancesPluginFolders,
|
||||
GlancesPluginFs,
|
||||
GlancesPluginNpu,
|
||||
GlancesPluginGpu,
|
||||
GlancesPluginHostname,
|
||||
GlancesPluginIp,
|
||||
GlancesPluginIrq,
|
||||
GlancesPluginLoad,
|
||||
GlancesPluginMem,
|
||||
GlancesPluginMemswap,
|
||||
GlancesPluginNetwork,
|
||||
GlancesPluginNow,
|
||||
GlancesPluginPercpu,
|
||||
GlancesPluginPorts,
|
||||
GlancesPluginProcess,
|
||||
GlancesPluginQuicklook,
|
||||
GlancesPluginRaid,
|
||||
GlancesPluginSensors,
|
||||
GlancesPluginSmart,
|
||||
GlancesPluginSystem,
|
||||
GlancesPluginUptime,
|
||||
GlancesPluginVms,
|
||||
GlancesPluginWifi,
|
||||
HeaderSection,
|
||||
MetricsCpu,
|
||||
MetricsMem,
|
||||
MetricsLoad,
|
||||
MetricsGpu,
|
||||
SidebarSection,
|
||||
MainProcess,
|
||||
FooterAlerts,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
store,
|
||||
};
|
||||
return { store };
|
||||
},
|
||||
computed: {
|
||||
args() {
|
||||
return this.store.args || {};
|
||||
},
|
||||
config() {
|
||||
return this.store.config || {};
|
||||
},
|
||||
data() {
|
||||
return this.store.data || {};
|
||||
},
|
||||
dataLoaded() {
|
||||
return this.store.data !== undefined;
|
||||
args() { return this.store.args || {}; },
|
||||
config() { return this.store.config || {}; },
|
||||
data() { return this.store.data || {}; },
|
||||
dataLoaded() { return this.store.data !== undefined; },
|
||||
hasGpu() {
|
||||
return this.store.data?.stats?.gpu && this.store.data.stats.gpu.length > 0;
|
||||
},
|
||||
hasNpu() {
|
||||
return this.store.data.stats.npu.length > 0;
|
||||
return this.store.data?.stats?.npu && this.store.data.stats.npu.length > 0;
|
||||
},
|
||||
hasGpu() {
|
||||
return this.store.data.stats.gpu.length > 0;
|
||||
},
|
||||
isLinux() {
|
||||
return this.store.data.isLinux;
|
||||
metricsClass() {
|
||||
const classes = [];
|
||||
if (this.args.disable_gpu || !this.hasGpu) classes.push('no-gpu');
|
||||
if (this.args.disable_load) classes.push('no-load');
|
||||
return classes.join(' ');
|
||||
},
|
||||
title() {
|
||||
const { data } = this;
|
||||
const title =
|
||||
(data.stats && data.stats.system && data.stats.system.hostname) || "";
|
||||
return title ? `${title} - Glances` : "Glances";
|
||||
},
|
||||
topMenu() {
|
||||
return this.config.outputs !== undefined &&
|
||||
this.config.outputs.top_menu !== undefined
|
||||
? this.config.outputs.top_menu.split(",")
|
||||
: uiconfig.topMenu;
|
||||
},
|
||||
leftMenu() {
|
||||
return this.config.outputs !== undefined &&
|
||||
this.config.outputs.left_menu !== undefined
|
||||
? this.config.outputs.left_menu.split(",")
|
||||
: uiconfig.leftMenu;
|
||||
const title = (data.stats && data.stats.system && data.stats.system.hostname) || "";
|
||||
return title ? `${title} \u2014 Glances` : "Glances";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
title() {
|
||||
if (document) {
|
||||
document.title = this.title;
|
||||
}
|
||||
if (document) document.title = this.title;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
@@ -229,181 +96,48 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
setupHotKeys() {
|
||||
// a => Sort processes/containers automatically
|
||||
hotkeys("a", () => {
|
||||
this.store.args.sort_processes_key = null;
|
||||
});
|
||||
// Sort keys
|
||||
hotkeys("a", () => { this.store.args.sort_processes_key = null; });
|
||||
hotkeys("c", () => { this.store.args.sort_processes_key = "cpu_percent"; });
|
||||
hotkeys("m", () => { this.store.args.sort_processes_key = "memory_percent"; });
|
||||
hotkeys("u", () => { this.store.args.sort_processes_key = "username"; });
|
||||
hotkeys("p", () => { this.store.args.sort_processes_key = "name"; });
|
||||
hotkeys("o", () => { this.store.args.sort_processes_key = "cpu_num"; });
|
||||
hotkeys("i", () => { this.store.args.sort_processes_key = "io_counters"; });
|
||||
hotkeys("t", () => { this.store.args.sort_processes_key = "timemillis"; });
|
||||
|
||||
// c => Sort processes/containers by CPU%
|
||||
hotkeys("c", () => {
|
||||
this.store.args.sort_processes_key = "cpu_percent";
|
||||
});
|
||||
|
||||
// m => Sort processes/containers by MEM%
|
||||
hotkeys("m", () => {
|
||||
this.store.args.sort_processes_key = "memory_percent";
|
||||
});
|
||||
|
||||
// u => Sort processes/containers by user
|
||||
hotkeys("u", () => {
|
||||
this.store.args.sort_processes_key = "username";
|
||||
});
|
||||
|
||||
// p => Sort processes/containers by name
|
||||
hotkeys("p", () => {
|
||||
this.store.args.sort_processes_key = "name";
|
||||
});
|
||||
|
||||
// o => Sort processes/containers by CPU core number
|
||||
hotkeys("o", () => {
|
||||
this.store.args.sort_processes_key = "cpu_num";
|
||||
});
|
||||
|
||||
// i => Sort processes/containers by I/O rate
|
||||
hotkeys("i", () => {
|
||||
this.store.args.sort_processes_key = "io_counters";
|
||||
});
|
||||
|
||||
// t => Sort processes/containers by time
|
||||
hotkeys("t", () => {
|
||||
this.store.args.sort_processes_key = "timemillis";
|
||||
});
|
||||
|
||||
// A => Enable/disable AMPs
|
||||
hotkeys("shift+A", () => {
|
||||
this.store.args.disable_amps = !this.store.args.disable_amps;
|
||||
});
|
||||
|
||||
// d => Show/hide disk I/O stats
|
||||
hotkeys("d", () => {
|
||||
this.store.args.disable_diskio = !this.store.args.disable_diskio;
|
||||
});
|
||||
|
||||
// Q => Show/hide IRQ
|
||||
hotkeys("shift+Q", () => {
|
||||
this.store.args.enable_irq = !this.store.args.enable_irq;
|
||||
});
|
||||
|
||||
// f => Show/hide filesystem stats
|
||||
hotkeys("f", () => {
|
||||
this.store.args.disable_fs = !this.store.args.disable_fs;
|
||||
});
|
||||
|
||||
// j => Accumulate processes by program
|
||||
hotkeys("j", () => {
|
||||
this.store.args.programs = !this.store.args.programs;
|
||||
});
|
||||
|
||||
// k => Show/hide connections stats
|
||||
hotkeys("k", () => {
|
||||
this.store.args.disable_connections =
|
||||
!this.store.args.disable_connections;
|
||||
});
|
||||
|
||||
// n => Show/hide network stats
|
||||
hotkeys("n", () => {
|
||||
this.store.args.disable_network = !this.store.args.disable_network;
|
||||
});
|
||||
|
||||
// s => Show/hide sensors stats
|
||||
hotkeys("s", () => {
|
||||
this.store.args.disable_sensors = !this.store.args.disable_sensors;
|
||||
});
|
||||
|
||||
// 2 => Show/hide left sidebar
|
||||
hotkeys("2", () => {
|
||||
this.store.args.disable_left_sidebar =
|
||||
!this.store.args.disable_left_sidebar;
|
||||
});
|
||||
|
||||
// z => Enable/disable processes stats
|
||||
hotkeys("z", () => {
|
||||
this.store.args.disable_process = !this.store.args.disable_process;
|
||||
});
|
||||
|
||||
// S => Enable/disable short processes name
|
||||
hotkeys("shift+S", () => {
|
||||
this.store.args.process_short_name =
|
||||
!this.store.args.process_short_name;
|
||||
});
|
||||
|
||||
// D => Enable/disable containers stats
|
||||
hotkeys("shift+D", () => {
|
||||
this.store.args.disable_containers =
|
||||
!this.store.args.disable_containers;
|
||||
});
|
||||
|
||||
// b => Bytes or bits for network I/O
|
||||
hotkeys("b", () => {
|
||||
this.store.args.byte = !this.store.args.byte;
|
||||
});
|
||||
|
||||
// 'B' => Switch between bit/s and IO/s for Disk IO
|
||||
// Toggle keys
|
||||
hotkeys("shift+A", () => { this.store.args.disable_amps = !this.store.args.disable_amps; });
|
||||
hotkeys("d", () => { this.store.args.disable_diskio = !this.store.args.disable_diskio; });
|
||||
hotkeys("shift+Q", () => { this.store.args.enable_irq = !this.store.args.enable_irq; });
|
||||
hotkeys("f", () => { this.store.args.disable_fs = !this.store.args.disable_fs; });
|
||||
hotkeys("j", () => { this.store.args.programs = !this.store.args.programs; });
|
||||
hotkeys("k", () => { this.store.args.disable_connections = !this.store.args.disable_connections; });
|
||||
hotkeys("n", () => { this.store.args.disable_network = !this.store.args.disable_network; });
|
||||
hotkeys("s", () => { this.store.args.disable_sensors = !this.store.args.disable_sensors; });
|
||||
hotkeys("2", () => { this.store.args.disable_left_sidebar = !this.store.args.disable_left_sidebar; });
|
||||
hotkeys("z", () => { this.store.args.disable_process = !this.store.args.disable_process; });
|
||||
hotkeys("shift+S", () => { this.store.args.process_short_name = !this.store.args.process_short_name; });
|
||||
hotkeys("shift+D", () => { this.store.args.disable_containers = !this.store.args.disable_containers; });
|
||||
hotkeys("b", () => { this.store.args.byte = !this.store.args.byte; });
|
||||
hotkeys("shift+B", () => {
|
||||
this.store.args.diskio_iops = !this.store.args.diskio_iops;
|
||||
if (this.store.args.diskio_iops) {
|
||||
this.store.args.diskio_latency = false;
|
||||
}
|
||||
if (this.store.args.diskio_iops) this.store.args.diskio_latency = false;
|
||||
});
|
||||
|
||||
// 'L' => Switch to latency for Disk IO
|
||||
hotkeys("shift+L", () => {
|
||||
this.store.args.diskio_latency = !this.store.args.diskio_latency;
|
||||
if (this.store.args.diskio_latency) {
|
||||
this.store.args.diskio_iops = false;
|
||||
}
|
||||
if (this.store.args.diskio_latency) this.store.args.diskio_iops = false;
|
||||
});
|
||||
|
||||
// l => Show/hide alert logs
|
||||
hotkeys("l", () => {
|
||||
this.store.args.disable_alert = !this.store.args.disable_alert;
|
||||
});
|
||||
|
||||
// 1 => Global CPU or per-CPU stats
|
||||
hotkeys("1", () => {
|
||||
this.store.args.percpu = !this.store.args.percpu;
|
||||
});
|
||||
|
||||
// h => Show/hide this help screen
|
||||
hotkeys("h", () => {
|
||||
this.store.args.help_tag = !this.store.args.help_tag;
|
||||
});
|
||||
|
||||
// T => View network I/O as combination
|
||||
hotkeys("shift+T", () => {
|
||||
this.store.args.network_sum = !this.store.args.network_sum;
|
||||
});
|
||||
|
||||
// U => View cumulative network I/O
|
||||
hotkeys("shift+U", () => {
|
||||
this.store.args.network_cumul = !this.store.args.network_cumul;
|
||||
});
|
||||
|
||||
// F => Show filesystem free space
|
||||
hotkeys("shift+F", () => {
|
||||
this.store.args.fs_free_space = !this.store.args.fs_free_space;
|
||||
});
|
||||
|
||||
// 3 => Enable/disable quick look plugin
|
||||
hotkeys("3", () => {
|
||||
this.store.args.disable_quicklook = !this.store.args.disable_quicklook;
|
||||
});
|
||||
|
||||
// 6 => Enable/disable mean gpu
|
||||
hotkeys("6", () => {
|
||||
this.store.args.meangpu = !this.store.args.meangpu;
|
||||
});
|
||||
|
||||
// 7 => Enable/disable mean gpu
|
||||
hotkeys("7", () => {
|
||||
this.store.args.disable_npu = !this.store.args.disable_npu;
|
||||
});
|
||||
|
||||
// G => Enable/disable gpu
|
||||
hotkeys("shift+G", () => {
|
||||
this.store.args.disable_gpu = !this.store.args.disable_gpu;
|
||||
});
|
||||
|
||||
hotkeys("l", () => { this.store.args.disable_alert = !this.store.args.disable_alert; });
|
||||
hotkeys("1", () => { this.store.args.percpu = !this.store.args.percpu; });
|
||||
hotkeys("h", () => { this.store.args.help_tag = !this.store.args.help_tag; });
|
||||
hotkeys("shift+T", () => { this.store.args.network_sum = !this.store.args.network_sum; });
|
||||
hotkeys("shift+U", () => { this.store.args.network_cumul = !this.store.args.network_cumul; });
|
||||
hotkeys("shift+F", () => { this.store.args.fs_free_space = !this.store.args.fs_free_space; });
|
||||
hotkeys("3", () => { this.store.args.disable_quicklook = !this.store.args.disable_quicklook; });
|
||||
hotkeys("6", () => { this.store.args.meangpu = !this.store.args.meangpu; });
|
||||
hotkeys("7", () => { this.store.args.disable_npu = !this.store.args.disable_npu; });
|
||||
hotkeys("shift+G", () => { this.store.args.disable_gpu = !this.store.args.disable_gpu; });
|
||||
hotkeys("5", () => {
|
||||
this.store.args.disable_quicklook = !this.store.args.disable_quicklook;
|
||||
this.store.args.disable_cpu = !this.store.args.disable_cpu;
|
||||
@@ -413,32 +147,12 @@ export default {
|
||||
this.store.args.disable_gpu = !this.store.args.disable_gpu;
|
||||
this.store.args.disable_npu = !this.store.args.disable_npu;
|
||||
});
|
||||
|
||||
// I => Show/hide IP module
|
||||
hotkeys("shift+I", () => {
|
||||
this.store.args.disable_ip = !this.store.args.disable_ip;
|
||||
});
|
||||
|
||||
// P => Enable/disable ports module
|
||||
hotkeys("shift+P", () => {
|
||||
this.store.args.disable_ports = !this.store.args.disable_ports;
|
||||
});
|
||||
|
||||
// V => Enable/disable VMs stats
|
||||
hotkeys("shift+V", () => {
|
||||
this.store.args.disable_vms = !this.store.args.disable_vms;
|
||||
});
|
||||
|
||||
// 'W' > Enable/Disable Wifi plugin
|
||||
hotkeys("shift+W", () => {
|
||||
this.store.args.disable_wifi = !this.store.args.disable_wifi;
|
||||
});
|
||||
|
||||
// 0 => Enable/disable IRIX mode (see issue #3158)
|
||||
hotkeys("0", () => {
|
||||
this.store.args.disable_irix = !this.store.args.disable_irix;
|
||||
});
|
||||
hotkeys("shift+I", () => { this.store.args.disable_ip = !this.store.args.disable_ip; });
|
||||
hotkeys("shift+P", () => { this.store.args.disable_ports = !this.store.args.disable_ports; });
|
||||
hotkeys("shift+V", () => { this.store.args.disable_vms = !this.store.args.disable_vms; });
|
||||
hotkeys("shift+W", () => { this.store.args.disable_wifi = !this.store.args.disable_wifi; });
|
||||
hotkeys("0", () => { this.store.args.disable_irix = !this.store.args.disable_irix; });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -3,11 +3,8 @@ if (module.hot) {
|
||||
module.hot.accept();
|
||||
}
|
||||
|
||||
import "../css/custom.scss";
|
||||
import "../css/style.scss";
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import * as filters from "./filters.js";
|
||||
|
||||
119
glances/outputs/static/js/components/footer-alerts.vue
Normal file
119
glances/outputs/static/js/components/footer-alerts.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<footer id="footer" v-if="hasAlerts && !cleared">
|
||||
<div class="foot-header">
|
||||
<span class="foot-label">Alerts</span>
|
||||
<span class="foot-count">{{ alertCount }}</span>
|
||||
<button class="foot-clear" @click="clearAlerts">[ CLEAR ]</button>
|
||||
</div>
|
||||
<div class="foot-alert" v-for="(alert, idx) in alerts" :key="idx">
|
||||
<div class="foot-dot" :class="alert.dotClass"></div>
|
||||
<span class="foot-time">{{ alert.time }}</span>
|
||||
<template v-if="alert.ongoing">
|
||||
<span class="foot-ongoing" :class="alert.ongoingClass">ongoing</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="foot-dur">{{ alert.duration }}</span>
|
||||
</template>
|
||||
<span class="foot-msg" :class="alert.msgClass">{{ alert.message }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GlancesFavico } from "../services.js";
|
||||
|
||||
export default {
|
||||
props: { data: { type: Object } },
|
||||
data() {
|
||||
return {
|
||||
cleared: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
alertCount(newVal) {
|
||||
// Reset cleared state when new alerts come in
|
||||
if (newVal > 0) this.cleared = false;
|
||||
// Update favicon badge
|
||||
if (newVal > 0) {
|
||||
GlancesFavico.badge(newVal);
|
||||
} else {
|
||||
GlancesFavico.reset();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
rawAlerts() {
|
||||
return this.data?.stats?.alert || [];
|
||||
},
|
||||
hasAlerts() {
|
||||
return this.rawAlerts.length > 0;
|
||||
},
|
||||
alertCount() {
|
||||
return this.rawAlerts.length;
|
||||
},
|
||||
alerts() {
|
||||
return this.rawAlerts.slice(0, 10).map(a => {
|
||||
// a = [begin_timestamp, end_timestamp, state, type, min, avg, max, top, count]
|
||||
// or {state, type, begin, end, min, avg, max, top}
|
||||
const isArray = Array.isArray(a);
|
||||
const state = isArray ? a[2] : a.state;
|
||||
const type = isArray ? a[3] : a.type;
|
||||
const begin = isArray ? a[0] * 1000 : (a.begin || 0) * 1000;
|
||||
const end = isArray ? a[1] * 1000 : (a.end || 0) * 1000;
|
||||
const maxVal = isArray ? a[6] : a.max;
|
||||
const top = isArray ? a[7] : a.top;
|
||||
|
||||
const ongoing = end === -1000 || end < 0;
|
||||
const stateLower = (state || '').toLowerCase();
|
||||
|
||||
// Time display
|
||||
const beginDate = new Date(begin);
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
const time = `${pad(beginDate.getHours())}:${pad(beginDate.getMinutes())}:${pad(beginDate.getSeconds())}`;
|
||||
|
||||
// Duration
|
||||
let duration = '';
|
||||
if (!ongoing && end > begin) {
|
||||
const diffSec = Math.round((end - begin) / 1000);
|
||||
const m = Math.floor(diffSec / 60);
|
||||
const s = diffSec % 60;
|
||||
duration = `${m}m${pad(s)}s`;
|
||||
}
|
||||
|
||||
// Dot class
|
||||
let dotClass = 'ok';
|
||||
if (stateLower === 'critical') dotClass = 'crit';
|
||||
else if (stateLower === 'warning') dotClass = 'warn';
|
||||
|
||||
// Ongoing class
|
||||
let ongoingClass = '';
|
||||
if (stateLower === 'critical') ongoingClass = 'crit';
|
||||
else if (stateLower === 'warning') ongoingClass = 'warn';
|
||||
|
||||
// Message class
|
||||
let msgClass = 'info';
|
||||
if (stateLower === 'critical') msgClass = 'critical';
|
||||
else if (stateLower === 'warning') msgClass = 'warning';
|
||||
|
||||
// Build message text
|
||||
let message = `${type}`;
|
||||
if (maxVal != null) message += ` (${typeof maxVal === 'number' ? maxVal.toFixed(1) : maxVal})`;
|
||||
if (top && top.length > 0) {
|
||||
const topStr = Array.isArray(top)
|
||||
? top.map(t => t.name || t).join(', ')
|
||||
: top;
|
||||
if (topStr) message += ` : ${topStr}`;
|
||||
}
|
||||
|
||||
return { time, ongoing, duration, dotClass, ongoingClass, msgClass, message };
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearAlerts() {
|
||||
this.cleared = true;
|
||||
GlancesFavico.reset();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
82
glances/outputs/static/js/components/header-section.vue
Normal file
82
glances/outputs/static/js/components/header-section.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<header id="header">
|
||||
<!-- System: hostname / OS -->
|
||||
<div class="hdr-system">
|
||||
<div class="hdr-hostname" :class="{ disconnected: isDisconnected }">
|
||||
<template v-if="isDisconnected">Disconnected from </template>{{ hostname }}
|
||||
</div>
|
||||
<div class="hdr-os">{{ humanReadableName }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Right side blocks -->
|
||||
<div class="hdr-right">
|
||||
<!-- IP block -->
|
||||
<div class="hdr-block" v-if="hasIp">
|
||||
<div class="hdr-ip-row">
|
||||
<span class="hdr-ip-label">LOCAL</span>
|
||||
<span class="hdr-ip-val private">{{ address }}{{ maskCidr ? '/' + maskCidr : '' }}</span>
|
||||
</div>
|
||||
<div class="hdr-ip-row">
|
||||
<span class="hdr-ip-label">PUBLIC</span>
|
||||
<span class="hdr-ip-val">{{ publicAddress }}</span>
|
||||
<span class="hdr-ip-location" v-if="publicInfo">— {{ publicInfo }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Uptime + clock block -->
|
||||
<div class="hdr-block hdr-block--right">
|
||||
<div>
|
||||
<span class="pulse-dot" :class="{ disconnected: isDisconnected }"></span>
|
||||
<span class="hdr-uptime">↑ {{ uptime }}</span>
|
||||
</div>
|
||||
<div class="hdr-clock">{{ clock }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { store } from "../store.js";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: { type: Object },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
clock: '',
|
||||
clockInterval: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
stats() { return this.data.stats || {}; },
|
||||
isDisconnected() { return store.status === 'FAILURE'; },
|
||||
hostname() { return this.stats.system?.hostname || ''; },
|
||||
humanReadableName() { return this.stats.system?.hr_name || ''; },
|
||||
hasIp() { return this.stats.ip && this.stats.ip.address; },
|
||||
address() { return this.stats.ip?.address || ''; },
|
||||
maskCidr() { return this.stats.ip?.mask_cidr || ''; },
|
||||
publicAddress() { return this.stats.ip?.public_address || ''; },
|
||||
publicInfo() { return this.stats.ip?.public_info_human || ''; },
|
||||
uptime() { return this.stats.uptime || ''; },
|
||||
},
|
||||
mounted() {
|
||||
this.updateClock();
|
||||
this.clockInterval = setInterval(() => this.updateClock(), 1000);
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.clockInterval) clearInterval(this.clockInterval);
|
||||
},
|
||||
methods: {
|
||||
updateClock() {
|
||||
if (this.stats.now?.custom) {
|
||||
this.clock = this.stats.now.custom;
|
||||
} else {
|
||||
const n = new Date();
|
||||
const p = x => String(x).padStart(2, '0');
|
||||
this.clock = `${n.getFullYear()}-${p(n.getMonth()+1)}-${p(n.getDate())} ${p(n.getHours())}:${p(n.getMinutes())}:${p(n.getSeconds())}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,170 +1,143 @@
|
||||
<template>
|
||||
<div v-if="help">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-lg-24 title">{{ help.version }} {{ help.psutil_version }}</div>
|
||||
</div>
|
||||
<div class="row"> </div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-lg-24">
|
||||
{{ help.configuration_file }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row"> </div>
|
||||
</div>
|
||||
<table class="table table-sm table-borderless table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ help.header_sort.replace(':', '') }}</th>
|
||||
<th>{{ help.header_show_hide.replace(':', '') }}</th>
|
||||
<th>{{ help.header_toggle.replace(':', '') }}</th>
|
||||
<th>{{ help.header_miscellaneous.replace(':', '') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ help.sort_auto }}</td>
|
||||
<td>{{ help.show_hide_application_monitoring }}</td>
|
||||
<td>{{ help.toggle_bits_bytes }}</td>
|
||||
<td>{{ help.misc_erase_process_filter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_cpu }}</td>
|
||||
<td>{{ help.show_hide_diskio }}</td>
|
||||
<td>{{ help.toggle_count_rate }}</td>
|
||||
<td>{{ help.misc_generate_history_graphs }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_io_rate }}</td>
|
||||
<td>{{ help.show_hide_containers }}</td>
|
||||
<td>{{ help.toggle_used_free }}</td>
|
||||
<td>{{ help.misc_help }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_cpu_num }}</td>
|
||||
<td>{{ help.show_hide_top_extended_stats }}</td>
|
||||
<td>{{ help.toggle_bar_sparkline }}</td>
|
||||
<td>{{ help.misc_accumulate_processes_by_program }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_mem }}</td>
|
||||
<td>{{ help.show_hide_top_extended_stats }}</td>
|
||||
<td>{{ help.toggle_bar_sparkline }}</td>
|
||||
<td>{{ help.misc_accumulate_processes_by_program }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_process_name }}</td>
|
||||
<td>{{ help.show_hide_filesystem }}</td>
|
||||
<td>{{ help.toggle_separate_combined }}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_cpu_times }}</td>
|
||||
<td>{{ help.show_hide_gpu }}</td>
|
||||
<td>{{ help.toggle_live_cumulative }}</td>
|
||||
<td>{{ help.misc_reset_processes_summary_min_max }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_user }}</td>
|
||||
<td>{{ help.show_hide_ip }}</td>
|
||||
<td>{{ help.toggle_linux_percentage }}</td>
|
||||
<td>{{ help.misc_quit }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_tcp_connection }}</td>
|
||||
<td>{{ help.toggle_cpu_individual_combined }}</td>
|
||||
<td>{{ help.misc_reset_history }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_alert }}</td>
|
||||
<td>{{ help.toggle_gpu_individual_combined }}</td>
|
||||
<td>{{ help.misc_delete_warning_alerts }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_network }}</td>
|
||||
<td>{{ help.toggle_short_full }}</td>
|
||||
<td>{{ help.misc_delete_warning_and_critical_alerts }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.sort_cpu_times }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_irq }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_raid_plugin }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_sensors }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_wifi_module }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_processes }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_left_sidebar }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_quick_look }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_cpu_mem_swap }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_all }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row"> </div>
|
||||
<div>
|
||||
<p>
|
||||
For an exhaustive list of key bindings,
|
||||
<a href="https://glances.readthedocs.io/en/latest/cmds.html#interactive-commands">click here</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p><a href="/docs">API documentation</a> / <a href="/openapi.json">OpenAPI file</a></p>
|
||||
</div>
|
||||
<div class="row"> </div>
|
||||
<div>
|
||||
<p>Press <b>h</b> to came back to Glances.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-screen" v-if="help">
|
||||
<h2>{{ help.version }} {{ help.psutil_version }}</h2>
|
||||
<p style="color:var(--fg2);margin-bottom:12px">{{ help.configuration_file }}</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ help.header_sort.replace(':', '') }}</th>
|
||||
<th>{{ help.header_show_hide.replace(':', '') }}</th>
|
||||
<th>{{ help.header_toggle.replace(':', '') }}</th>
|
||||
<th>{{ help.header_miscellaneous.replace(':', '') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ help.sort_auto }}</td>
|
||||
<td>{{ help.show_hide_application_monitoring }}</td>
|
||||
<td>{{ help.toggle_bits_bytes }}</td>
|
||||
<td>{{ help.misc_erase_process_filter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_cpu }}</td>
|
||||
<td>{{ help.show_hide_diskio }}</td>
|
||||
<td>{{ help.toggle_count_rate }}</td>
|
||||
<td>{{ help.misc_generate_history_graphs }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_io_rate }}</td>
|
||||
<td>{{ help.show_hide_containers }}</td>
|
||||
<td>{{ help.toggle_used_free }}</td>
|
||||
<td>{{ help.misc_help }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_cpu_num }}</td>
|
||||
<td>{{ help.show_hide_top_extended_stats }}</td>
|
||||
<td>{{ help.toggle_bar_sparkline }}</td>
|
||||
<td>{{ help.misc_accumulate_processes_by_program }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_mem }}</td>
|
||||
<td>{{ help.show_hide_filesystem }}</td>
|
||||
<td>{{ help.toggle_separate_combined }}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_process_name }}</td>
|
||||
<td>{{ help.show_hide_gpu }}</td>
|
||||
<td>{{ help.toggle_live_cumulative }}</td>
|
||||
<td>{{ help.misc_reset_processes_summary_min_max }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_cpu_times }}</td>
|
||||
<td>{{ help.show_hide_ip }}</td>
|
||||
<td>{{ help.toggle_linux_percentage }}</td>
|
||||
<td>{{ help.misc_quit }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ help.sort_user }}</td>
|
||||
<td>{{ help.show_hide_tcp_connection }}</td>
|
||||
<td>{{ help.toggle_cpu_individual_combined }}</td>
|
||||
<td>{{ help.misc_reset_history }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_alert }}</td>
|
||||
<td>{{ help.toggle_gpu_individual_combined }}</td>
|
||||
<td>{{ help.misc_delete_warning_alerts }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_network }}</td>
|
||||
<td>{{ help.toggle_short_full }}</td>
|
||||
<td>{{ help.misc_delete_warning_and_critical_alerts }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_irq }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_raid_plugin }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_sensors }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_wifi_module }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_processes }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_left_sidebar }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_quick_look }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_cpu_mem_swap }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ help.show_hide_all }}</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="margin-top:12px">
|
||||
For an exhaustive list of key bindings,
|
||||
<a href="https://glances.readthedocs.io/en/latest/cmds.html#interactive-commands" style="color:var(--cyan)">click here</a>.
|
||||
</p>
|
||||
<p style="margin-top:6px">
|
||||
<a href="/docs" style="color:var(--cyan)">API documentation</a> /
|
||||
<a href="/openapi.json" style="color:var(--cyan)">OpenAPI file</a>
|
||||
</p>
|
||||
<p style="margin-top:12px;color:var(--fg2)">Press <strong style="color:var(--cyan)">h</strong> to go back to Glances.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -180,4 +153,4 @@ export default {
|
||||
.then((response) => (this.help = response));
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
340
glances/outputs/static/js/components/main-process.vue
Normal file
340
glances/outputs/static/js/components/main-process.vue
Normal file
@@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<main id="main">
|
||||
<!-- Containers section -->
|
||||
<template v-if="!args.disable_containers && hasContainers">
|
||||
<div class="main-section-title">Containers ({{ containers.length }})</div>
|
||||
<div class="proc-wrap" style="flex:none;max-height:120px">
|
||||
<table class="proc-table container-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ct-name">Name</th>
|
||||
<th class="ct-status">Status</th>
|
||||
<th class="ct-cpu">CPU%</th>
|
||||
<th class="ct-mem">MEM</th>
|
||||
<th class="ct-cmd">Command</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="c in containers" :key="c.id">
|
||||
<td class="ct-name" style="color:var(--fg)">{{ c.name }}</td>
|
||||
<td class="ct-status" :class="c.statusClass">{{ c.status }}</td>
|
||||
<td class="ct-cpu" :class="c.cpuClass">{{ c.cpu }}</td>
|
||||
<td class="ct-mem">{{ c.mem }}</td>
|
||||
<td class="ct-cmd cmd-cell">{{ c.command }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- AMPs section -->
|
||||
<template v-if="!args.disable_amps && hasAmps">
|
||||
<div class="main-section-title">AMPs</div>
|
||||
<div class="amps-wrap">
|
||||
<div class="amps-row" v-for="(amp, idx) in amps" :key="idx">
|
||||
<span class="amps-name" :class="amp.deco">{{ amp.name }}</span>
|
||||
<span class="amps-count" v-if="amp.regex">{{ amp.count }}</span>
|
||||
<span class="amps-result" v-html="amp.result"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Process toolbar -->
|
||||
<div class="main-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="toolbar-title">{{ args.programs ? 'Programs' : 'Processes' }}</span>
|
||||
<span class="toolbar-count">
|
||||
<strong>{{ processTotal }}</strong> tasks ·
|
||||
<strong>{{ processThread }}</strong> thr ·
|
||||
<strong style="color:var(--green)">{{ processRunning }}</strong> run ·
|
||||
<strong>{{ processSleeping }}</strong> slp
|
||||
</span>
|
||||
<span class="toolbar-sort">sorted by <span class="active">{{ sortLabel }}</span></span>
|
||||
</div>
|
||||
<div style="display:flex;gap:5px">
|
||||
<button class="t-pill" :class="{ active: isSort('io_counters') }" @click="setSort('io_counters')">IO</button>
|
||||
<button class="t-pill" :class="{ active: isSort('memory_percent') }" @click="setSort('memory_percent')">MEM</button>
|
||||
<button class="t-pill" :class="{ active: isSort('cpu_percent') }" @click="setSort('cpu_percent')">CPU ↓</button>
|
||||
<button class="t-pill" :class="{ active: isSort(null) }" @click="setSort(null)">AUTO</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Process table -->
|
||||
<div class="proc-wrap">
|
||||
<table class="proc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-cpu" :class="{ 'sort-active': isSort('cpu_percent') }" @click="setSort('cpu_percent')">CPU%</th>
|
||||
<th class="col-mem" :class="{ 'sort-active': isSort('memory_percent') }" @click="setSort('memory_percent')">MEM%</th>
|
||||
<th class="col-virt">VIRT</th>
|
||||
<th class="col-res">RES</th>
|
||||
<th class="col-pid">PID</th>
|
||||
<th class="col-ni" v-if="!data.isWindows">NI</th>
|
||||
<th class="col-s">S</th>
|
||||
<th class="col-ior" :class="{ 'sort-active': isSort('io_counters') }" @click="setSort('io_counters')">IOR/s</th>
|
||||
<th class="col-iow">IOW/s</th>
|
||||
<th class="col-user" :class="{ 'sort-active': isSort('username') }" @click="setSort('username')">USER</th>
|
||||
<th class="col-time" :class="{ 'sort-active': isSort('timemillis') }" @click="setSort('timemillis')">TIME+</th>
|
||||
<th class="col-cmd" :class="{ 'sort-active': isSort('name') }" @click="setSort('name')" style="text-align:left">Command</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="proc in processList" :key="proc.pid" @click="toggleExtended(proc.pid)">
|
||||
<td :class="proc.cpuClass">
|
||||
<div class="cpu-bar-wrap">
|
||||
<span style="min-width:32px;text-align:right">{{ proc.cpu }}</span>
|
||||
<div class="cpu-ibar">
|
||||
<div class="cpu-ibar-fill" :class="proc.cpuBarClass" :style="{ width: proc.cpuBarWidth + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td :class="proc.memClass">{{ proc.mem }}</td>
|
||||
<td>{{ proc.vms }}</td>
|
||||
<td>{{ proc.rss }}</td>
|
||||
<td style="color:var(--fg3)">{{ proc.pid }}</td>
|
||||
<td v-if="!data.isWindows" style="color:var(--fg3)">{{ proc.nice }}</td>
|
||||
<td><span class="proc-status" :class="proc.status">{{ proc.status }}</span></td>
|
||||
<td style="color:var(--fg3)">{{ proc.ioRead }}</td>
|
||||
<td style="color:var(--fg3)">{{ proc.ioWrite }}</td>
|
||||
<td style="color:var(--fg2)">{{ proc.username }}</td>
|
||||
<td style="color:var(--fg3)">{{ proc.timeStr }}</td>
|
||||
<td>
|
||||
<span class="cmd-cell" :class="proc.cmdClass" :title="proc.cmdFull">{{ proc.cmdDisplay }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { orderBy } from "lodash";
|
||||
import { store } from "../store.js";
|
||||
import { GlancesHelper } from "../services.js";
|
||||
|
||||
const REVERSE_COLUMNS = new Set([
|
||||
'cpu_percent', 'memory_percent', 'io_counters', 'num_threads',
|
||||
]);
|
||||
|
||||
export default {
|
||||
props: { data: { type: Object } },
|
||||
computed: {
|
||||
args() { return store.args || {}; },
|
||||
config() { return store.config || {}; },
|
||||
|
||||
// ── PROCESS COUNT ──
|
||||
countStats() { return this.data?.stats?.processcount || {}; },
|
||||
processTotal() { return this.countStats.total || 0; },
|
||||
processThread() { return this.countStats.thread || 0; },
|
||||
processRunning() { return this.countStats.running || 0; },
|
||||
processSleeping() { return this.countStats.sleeping || 0; },
|
||||
|
||||
// ── SORT ──
|
||||
currentSort() { return store.args?.sort_processes_key || null; },
|
||||
sortLabel() {
|
||||
const map = {
|
||||
cpu_percent: 'CPU',
|
||||
memory_percent: 'MEM',
|
||||
username: 'USER',
|
||||
name: 'NAME',
|
||||
io_counters: 'IO',
|
||||
timemillis: 'TIME',
|
||||
cpu_num: 'CORE',
|
||||
};
|
||||
return map[this.currentSort] || 'AUTO';
|
||||
},
|
||||
|
||||
// ── PROCESS LIST ──
|
||||
limit() {
|
||||
const cfgMax = this.config.outputs?.max_processes_display;
|
||||
return cfgMax ? parseInt(cfgMax) : 50;
|
||||
},
|
||||
processList() {
|
||||
const isPrograms = this.args.programs;
|
||||
const rawList = isPrograms
|
||||
? this.data?.stats?.programlist || []
|
||||
: this.data?.stats?.processlist || [];
|
||||
const cores = this.data?.stats?.core?.log || 1;
|
||||
const isIrix = !this.args.disable_irix;
|
||||
const isShort = this.args.process_short_name;
|
||||
const fmt = this.$filters.bytes;
|
||||
const fmtTd = this.$filters.timedelta;
|
||||
|
||||
// Sort raw data before slicing
|
||||
const sortCol = this.currentSort || 'cpu_percent';
|
||||
let sortKeys = [sortCol];
|
||||
if (sortCol === 'io_counters') {
|
||||
sortKeys = ['io_total_raw'];
|
||||
} else if (sortCol === 'timemillis') {
|
||||
sortKeys = ['cpu_times_total'];
|
||||
}
|
||||
const sortDir = REVERSE_COLUMNS.has(sortCol) ? 'desc' : 'asc';
|
||||
|
||||
// Pre-compute sortable values on raw items
|
||||
const enriched = rawList.map(p => {
|
||||
const tsu = p.time_since_update || 1;
|
||||
p.io_read_raw = p.io_counters ? (p.io_counters[0] - p.io_counters[2]) / tsu : 0;
|
||||
p.io_write_raw = p.io_counters ? (p.io_counters[1] - p.io_counters[3]) / tsu : 0;
|
||||
p.io_total_raw = p.io_read_raw + p.io_write_raw;
|
||||
p.cpu_times_total = p.cpu_times
|
||||
? (p.cpu_times.user || p.cpu_times[0] || 0) + (p.cpu_times.system || p.cpu_times[1] || 0)
|
||||
: 0;
|
||||
return p;
|
||||
});
|
||||
|
||||
const sorted = orderBy(enriched, sortKeys, sortKeys.map(() => sortDir));
|
||||
|
||||
return sorted.slice(0, this.limit).map(p => {
|
||||
let cpuVal = p.cpu_percent || 0;
|
||||
if (!isIrix && cores > 1) {
|
||||
cpuVal = cpuVal / cores;
|
||||
}
|
||||
|
||||
const cpuClass = this.getCpuClass(cpuVal);
|
||||
const cpuBarWidth = Math.min(cpuVal * 2.5, 100);
|
||||
const cpuBarClass = cpuClass || 'ok';
|
||||
|
||||
const memVal = p.memory_percent || 0;
|
||||
const memClass = this.getMemClass(memVal);
|
||||
|
||||
// VIRT / RES from memory_info object
|
||||
let memVirt = 0, memRes = 0;
|
||||
if (p.memory_info) {
|
||||
if (typeof p.memory_info === 'object' && !Array.isArray(p.memory_info)) {
|
||||
memVirt = p.memory_info.vms || 0;
|
||||
memRes = p.memory_info.rss || 0;
|
||||
} else if (Array.isArray(p.memory_info)) {
|
||||
memRes = p.memory_info[0] || 0;
|
||||
memVirt = p.memory_info[1] || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// I/O formatted
|
||||
const ioRead = fmt(Math.max(0, p.io_read_raw));
|
||||
const ioWrite = fmt(Math.max(0, p.io_write_raw));
|
||||
|
||||
// Time: timedelta expects array [user, system]
|
||||
let timeStr = '?';
|
||||
if (p.cpu_times) {
|
||||
const timesArray = Array.isArray(p.cpu_times)
|
||||
? p.cpu_times
|
||||
: [p.cpu_times.user || 0, p.cpu_times.system || 0];
|
||||
const td = fmtTd(timesArray);
|
||||
if (td) {
|
||||
timeStr = String(td.hours).padStart(2, '0') + ':' +
|
||||
String(td.minutes).padStart(2, '0') + ':' +
|
||||
String(td.seconds).padStart(2, '0');
|
||||
}
|
||||
}
|
||||
|
||||
// Command display
|
||||
let cmdline = p.cmdline;
|
||||
if (Array.isArray(cmdline)) {
|
||||
cmdline = cmdline.join(' ').replace(/\n/g, ' ');
|
||||
}
|
||||
const name = isShort ? (p.name || '') : (cmdline || p.name || '');
|
||||
const cmdFull = cmdline || p.name || '';
|
||||
|
||||
const maxLen = 80;
|
||||
const cmdDisplay = name.length > maxLen ? name.substring(0, maxLen) + '\u2026' : name;
|
||||
|
||||
const cmdClass = '';
|
||||
|
||||
return {
|
||||
pid: p.pid,
|
||||
cpu: cpuVal.toFixed(1),
|
||||
cpuVal,
|
||||
cpuClass,
|
||||
cpuBarWidth,
|
||||
cpuBarClass,
|
||||
mem: memVal.toFixed(1),
|
||||
memClass,
|
||||
vms: fmt(memVirt),
|
||||
rss: fmt(memRes),
|
||||
nice: p.nice ?? '',
|
||||
status: p.status || 'S',
|
||||
ioRead,
|
||||
ioWrite,
|
||||
username: p.username || '',
|
||||
timeStr,
|
||||
cmdDisplay,
|
||||
cmdFull,
|
||||
cmdClass,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// ── CONTAINERS ──
|
||||
hasContainers() {
|
||||
const c = this.data?.stats?.containers;
|
||||
return c && c.length > 0;
|
||||
},
|
||||
containers() {
|
||||
const list = this.data?.stats?.containers || [];
|
||||
const fmt = this.$filters.bytes;
|
||||
return list.slice(0, 10).map(c => {
|
||||
const status = c.Status || c.status || 'unknown';
|
||||
const statusClass = status.startsWith('Up') || status === 'running' ? 'ok' : 'critical';
|
||||
return {
|
||||
id: c.Id || c.id,
|
||||
name: c.name || '',
|
||||
status,
|
||||
statusClass,
|
||||
cpu: (c.cpu?.total || 0).toFixed(1),
|
||||
cpuClass: (c.cpu?.total || 0) > 50 ? 'warning' : '',
|
||||
mem: fmt(c.memory?.usage || 0),
|
||||
command: c.Command || c.command || '',
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// ── AMPS ──
|
||||
hasAmps() {
|
||||
const a = this.data?.stats?.amps;
|
||||
return a && a.filter(p => p.result !== null).length > 0;
|
||||
},
|
||||
amps() {
|
||||
const list = this.data?.stats?.amps || [];
|
||||
const nl2br = this.$filters.nl2br;
|
||||
return list.filter(p => p.result !== null).map(p => {
|
||||
let deco = 'ok';
|
||||
if (p.count > 0) {
|
||||
if ((p.countmin !== null && p.count < p.countmin) ||
|
||||
(p.countmax !== null && p.count > p.countmax)) {
|
||||
deco = 'careful';
|
||||
}
|
||||
} else {
|
||||
deco = p.countmin === null ? 'ok' : 'critical';
|
||||
}
|
||||
return {
|
||||
name: p.name,
|
||||
count: p.count,
|
||||
regex: p.regex,
|
||||
result: nl2br(p.result),
|
||||
deco,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isSort(key) {
|
||||
return this.currentSort === key;
|
||||
},
|
||||
setSort(key) {
|
||||
store.args.sort_processes_key = key;
|
||||
},
|
||||
toggleExtended(pid) {
|
||||
fetch(`api/4/processes/extended/${pid}`, { method: 'POST' });
|
||||
},
|
||||
getCpuClass(val) {
|
||||
const alert = GlancesHelper.getAlert('processlist', 'processlist_cpu_', val, 100);
|
||||
return alert || '';
|
||||
},
|
||||
getMemClass(val) {
|
||||
const alert = GlancesHelper.getAlert('processlist', 'processlist_mem_', val, 100);
|
||||
return alert || '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
117
glances/outputs/static/js/components/metrics-cpu.vue
Normal file
117
glances/outputs/static/js/components/metrics-cpu.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="m-tile" style="--t-color:var(--green)">
|
||||
<div class="m-head">
|
||||
<span class="m-id">CPU <span class="m-id-sub" v-if="cpuName">{{ cpuName }} · {{ coreCount }}-core</span></span>
|
||||
</div>
|
||||
<div class="m-spark-row">
|
||||
<span class="m-val" :class="decoration">{{ totalDisplay }}<span class="unit">%</span></span>
|
||||
<sparkline :data="history" :max="100" :color="sparkColor" :height="22" />
|
||||
</div>
|
||||
<!-- 3-column extended stats grid -->
|
||||
<div class="cpu-stats-grid">
|
||||
<!-- col 1 -->
|
||||
<div class="cpu-col">
|
||||
<div class="cpu-row"><span class="k">user:</span><span class="v" :class="userDeco">{{ user }}%</span></div>
|
||||
<div class="cpu-row"><span class="k">system:</span><span class="v" :class="systemDeco">{{ system }}%</span></div>
|
||||
<div class="cpu-row"><span class="k">iowait:</span><span class="v" :class="iowaitDeco">{{ iowait }}%</span></div>
|
||||
<div class="cpu-row" v-if="data.isLinux"><span class="k">steal:</span><span class="v dim">{{ steal }}%</span></div>
|
||||
<div class="cpu-row" v-if="data.isWindows"><span class="k">dpc:</span><span class="v" :class="dpcDeco">{{ dpc }}%</span></div>
|
||||
</div>
|
||||
<!-- col 2 -->
|
||||
<div class="cpu-col">
|
||||
<div class="cpu-row"><span class="k">idle:</span><span class="v hi">{{ idle }}%</span></div>
|
||||
<div class="cpu-row"><span class="k">irq:</span><span class="v dim">{{ irq }}%</span></div>
|
||||
<div class="cpu-row"><span class="k">nice:</span><span class="v dim">{{ nice }}%</span></div>
|
||||
<div class="cpu-row" v-if="data.isLinux"><span class="k">guest:</span><span class="v dim">{{ guest }}%</span></div>
|
||||
</div>
|
||||
<!-- col 3 -->
|
||||
<div class="cpu-col">
|
||||
<div class="cpu-row"><span class="k">ctx_sw:</span><span class="v" :class="ctxDeco">{{ ctxSwitches }}</span></div>
|
||||
<div class="cpu-row"><span class="k">inter:</span><span class="v dim">{{ interrupts }}</span></div>
|
||||
<div class="cpu-row" v-if="data.isLinux"><span class="k">sw_int:</span><span class="v dim">{{ softInterrupts }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { store } from "../store.js";
|
||||
import Sparkline from "./sparkline.vue";
|
||||
|
||||
function colorFor(pct) {
|
||||
if (pct >= 90) return '#ff3355';
|
||||
if (pct >= 75) return '#ffcc00';
|
||||
if (pct >= 50) return '#4488ff';
|
||||
return '#00ff88';
|
||||
}
|
||||
|
||||
function decoClass(v) {
|
||||
if (!v || !v.decoration) return 'dim';
|
||||
const d = v.decoration.toLowerCase();
|
||||
if (d === 'ok' || d === 'ok_log') return 'ok';
|
||||
if (d === 'warning' || d === 'warning_log') return 'warn';
|
||||
if (d === 'critical' || d === 'critical_log') return 'crit';
|
||||
if (d === 'careful' || d === 'careful_log') return 'hi';
|
||||
if (d === 'default') return 'dim';
|
||||
return 'dim';
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { Sparkline },
|
||||
props: { data: { type: Object } },
|
||||
computed: {
|
||||
stats() { return this.data?.stats?.cpu || {}; },
|
||||
view() { return this.data?.views?.cpu || {}; },
|
||||
history() { return store.history.cpu; },
|
||||
total() { return this.stats.total != null ? this.stats.total : 0; },
|
||||
totalDisplay() { return this.total.toFixed(1); },
|
||||
decoration() {
|
||||
if (!this.view.total) return 'ok';
|
||||
const d = this.view.total.decoration?.toLowerCase() || 'ok';
|
||||
return d.replace('_log', '');
|
||||
},
|
||||
decorationLabel() {
|
||||
const d = this.decoration;
|
||||
if (d === 'ok') return 'OK';
|
||||
if (d === 'critical') return 'CRIT';
|
||||
if (d === 'warning') return 'WARN';
|
||||
return d.toUpperCase();
|
||||
},
|
||||
sparkColor() { return colorFor(this.total); },
|
||||
cpuName() { return this.data?.stats?.quicklook?.cpu_name || ''; },
|
||||
coreCount() { return this.data?.stats?.core?.log || this.data?.stats?.core?.phys || '?'; },
|
||||
user() { return (this.stats.user ?? 0).toFixed(1); },
|
||||
system() { return (this.stats.system ?? 0).toFixed(1); },
|
||||
idle() { return (this.stats.idle ?? 0).toFixed(1); },
|
||||
nice() { return (this.stats.nice ?? 0).toFixed(1); },
|
||||
irq() { return (this.stats.irq ?? 0).toFixed(1); },
|
||||
iowait() { return (this.stats.iowait ?? 0).toFixed(1); },
|
||||
steal() { return (this.stats.steal ?? 0).toFixed(1); },
|
||||
guest() { return (this.stats.guest ?? 0).toFixed(1); },
|
||||
dpc() { return (this.stats.dpc ?? 0).toFixed(1); },
|
||||
ctxSwitches() {
|
||||
const v = this.stats.ctx_switches;
|
||||
if (v == null) return '0';
|
||||
const tsu = this.stats.time_since_update || 1;
|
||||
return Math.round(v / tsu).toLocaleString();
|
||||
},
|
||||
interrupts() {
|
||||
const v = this.stats.interrupts;
|
||||
if (v == null) return '0';
|
||||
const tsu = this.stats.time_since_update || 1;
|
||||
return Math.round(v / tsu).toLocaleString();
|
||||
},
|
||||
softInterrupts() {
|
||||
const v = this.stats.soft_interrupts;
|
||||
if (v == null) return '0';
|
||||
const tsu = this.stats.time_since_update || 1;
|
||||
return Math.round(v / tsu).toLocaleString();
|
||||
},
|
||||
userDeco() { return decoClass(this.view.user); },
|
||||
systemDeco() { return decoClass(this.view.system); },
|
||||
iowaitDeco() { return decoClass(this.view.iowait); },
|
||||
dpcDeco() { return decoClass(this.view.dpc); },
|
||||
ctxDeco() { return decoClass(this.view.ctx_switches); },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
83
glances/outputs/static/js/components/metrics-gpu.vue
Normal file
83
glances/outputs/static/js/components/metrics-gpu.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="m-tile tile-gpu" style="--t-color:var(--cyan)">
|
||||
<div class="m-head">
|
||||
<span class="m-id">GPU <template v-if="gpuCount > 1">×{{ gpuCount }}</template></span>
|
||||
</div>
|
||||
<div class="m-spark-row">
|
||||
<span class="m-val" :class="decoration">{{ meanProcDisplay }}<span class="unit">%</span></span>
|
||||
<sparkline :data="history" :max="100" :color="sparkColor" :height="26" />
|
||||
</div>
|
||||
<div class="m-sub-rows">
|
||||
<div class="m-kv" v-for="gpu in gpus" :key="gpu.gpu_id">
|
||||
{{ gpu.name }}
|
||||
<span class="v" :class="gpuProcDeco(gpu)">{{ (gpu.proc || 0).toFixed(0) }}%</span>
|
||||
</div>
|
||||
<div class="m-kv" v-if="meanMem != null">
|
||||
MEM <span class="v dim">{{ meanMem.toFixed(0) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { store } from "../store.js";
|
||||
import Sparkline from "./sparkline.vue";
|
||||
|
||||
function colorFor(pct) {
|
||||
if (pct >= 90) return '#ff3355';
|
||||
if (pct >= 75) return '#ffcc00';
|
||||
if (pct >= 50) return '#4488ff';
|
||||
return '#00ff88';
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { Sparkline },
|
||||
props: { data: { type: Object } },
|
||||
computed: {
|
||||
stats() { return this.data?.stats?.gpu || []; },
|
||||
view() { return this.data?.views?.gpu || {}; },
|
||||
history() { return store.history.gpu; },
|
||||
gpus() { return this.stats; },
|
||||
gpuCount() { return this.stats.length; },
|
||||
meanProc() {
|
||||
if (this.stats.length === 0) return 0;
|
||||
return this.stats.reduce((s, g) => s + (g.proc || 0), 0) / this.stats.length;
|
||||
},
|
||||
meanProcDisplay() { return this.meanProc.toFixed(1); },
|
||||
meanMem() {
|
||||
if (this.stats.length === 0) return null;
|
||||
return this.stats.reduce((s, g) => s + (g.mem || 0), 0) / this.stats.length;
|
||||
},
|
||||
decoration() {
|
||||
// Use first GPU's decoration or derive from value
|
||||
const first = this.stats[0];
|
||||
if (first && this.view[first.gpu_id]?.proc?.decoration) {
|
||||
return this.view[first.gpu_id].proc.decoration.toLowerCase();
|
||||
}
|
||||
if (this.meanProc >= 90) return 'critical';
|
||||
if (this.meanProc >= 75) return 'warning';
|
||||
if (this.meanProc >= 50) return 'careful';
|
||||
return 'ok';
|
||||
},
|
||||
decorationLabel() {
|
||||
const d = this.decoration;
|
||||
if (d === 'ok') return 'OK';
|
||||
if (d === 'critical') return 'CRIT';
|
||||
if (d === 'warning') return 'WARN';
|
||||
return d.toUpperCase();
|
||||
},
|
||||
sparkColor() { return colorFor(this.meanProc); },
|
||||
},
|
||||
methods: {
|
||||
gpuProcDeco(gpu) {
|
||||
if (this.view[gpu.gpu_id]?.proc?.decoration) {
|
||||
const d = this.view[gpu.gpu_id].proc.decoration.toLowerCase();
|
||||
if (d === 'ok') return 'ok';
|
||||
if (d === 'warning') return 'warn';
|
||||
if (d === 'critical') return 'crit';
|
||||
}
|
||||
return 'ok';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
76
glances/outputs/static/js/components/metrics-load.vue
Normal file
76
glances/outputs/static/js/components/metrics-load.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="m-tile tile-load" style="--t-color:var(--green)">
|
||||
<div class="m-head">
|
||||
<span class="m-id">LOAD</span>
|
||||
</div>
|
||||
<div class="m-spark-row">
|
||||
<span class="m-val" :class="decoration">{{ min1Display }}</span>
|
||||
<sparkline :data="history" :max="cpucore" :color="sparkColor" :height="26" />
|
||||
</div>
|
||||
<div class="m-sub-rows">
|
||||
<div class="m-kv">1min <span class="v" :class="min1Deco">{{ min1Display }}</span></div>
|
||||
<div class="m-kv">5min <span class="v" :class="min5Deco">{{ min5Display }}</span></div>
|
||||
<div class="m-kv">15min <span class="v" :class="min15Deco">{{ min15Display }}</span></div>
|
||||
<div class="m-kv">cores <span class="v hi">{{ cpucore }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { store } from "../store.js";
|
||||
import Sparkline from "./sparkline.vue";
|
||||
|
||||
function colorFor(pct) {
|
||||
if (pct >= 90) return '#ff3355';
|
||||
if (pct >= 75) return '#ffcc00';
|
||||
if (pct >= 50) return '#4488ff';
|
||||
return '#00ff88';
|
||||
}
|
||||
|
||||
function decoStr(v) {
|
||||
if (!v || !v.decoration) return '';
|
||||
const d = v.decoration.toLowerCase();
|
||||
if (d === 'ok' || d === 'ok_log') return 'ok';
|
||||
if (d === 'warning' || d === 'warning_log') return 'warn';
|
||||
if (d === 'critical' || d === 'critical_log') return 'crit';
|
||||
if (d === 'careful' || d === 'careful_log') return 'hi';
|
||||
if (d === 'default') return 'dim';
|
||||
return 'dim';
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { Sparkline },
|
||||
props: { data: { type: Object } },
|
||||
computed: {
|
||||
stats() { return this.data?.stats?.load || {}; },
|
||||
view() { return this.data?.views?.load || {}; },
|
||||
history() { return store.history.load; },
|
||||
cpucore() { return this.stats.cpucore || 1; },
|
||||
min1() { return this.stats.min1 ?? 0; },
|
||||
min5() { return this.stats.min5 ?? 0; },
|
||||
min15() { return this.stats.min15 ?? 0; },
|
||||
min1Display() { return this.min1.toFixed(2); },
|
||||
min5Display() { return this.min5.toFixed(2); },
|
||||
min15Display() { return this.min15.toFixed(2); },
|
||||
decoration() {
|
||||
if (!this.view.min1) return 'ok';
|
||||
const d = this.view.min1.decoration?.toLowerCase() || 'ok';
|
||||
return d.replace('_log', '');
|
||||
},
|
||||
decorationLabel() {
|
||||
const d = this.decoration;
|
||||
if (d === 'ok') return 'OK';
|
||||
if (d === 'critical') return 'CRIT';
|
||||
if (d === 'warning') return 'WARN';
|
||||
return d.toUpperCase();
|
||||
},
|
||||
sparkColor() {
|
||||
const pct = this.cpucore > 0 ? (this.min1 / this.cpucore) * 100 : 0;
|
||||
return colorFor(pct);
|
||||
},
|
||||
min1Deco() { return decoStr(this.view.min1); },
|
||||
min5Deco() { return decoStr(this.view.min5); },
|
||||
min15Deco() { return decoStr(this.view.min15); },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
95
glances/outputs/static/js/components/metrics-mem.vue
Normal file
95
glances/outputs/static/js/components/metrics-mem.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="m-tile tile-mem" style="--t-color:var(--yellow)">
|
||||
<!-- header: MEM badge left, SWAP badge right -->
|
||||
<div class="ms-heads">
|
||||
<div class="ms-heads-left">
|
||||
<span class="ms-id">MEM</span>
|
||||
<span class="m-badge" :class="memDecoration">{{ memPercent }}%</span>
|
||||
</div>
|
||||
<div class="ms-heads-right" v-if="hasSwap">
|
||||
<span class="ms-id">SWAP</span>
|
||||
<span class="m-badge" :class="swapDecoration">{{ swapPercent }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MEM sparkline row -->
|
||||
<div class="ms-sparks">
|
||||
<div class="m-spark-row">
|
||||
<span class="ms-val" :class="memDecoration">{{ memPercent }}%</span>
|
||||
<sparkline :data="history" :max="100" :color="sparkColor" :height="22" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- extended MEM stats in 2-column grid -->
|
||||
<div class="ms-stats">
|
||||
<div class="ms-stat-row"><span class="k">total:</span><span class="v">{{ total }}</span></div>
|
||||
<div class="ms-stat-row"><span class="k">active:</span><span class="v">{{ active }}</span></div>
|
||||
<div class="ms-stat-row"><span class="k">used:</span><span class="v" :class="usedDeco">{{ used }}</span></div>
|
||||
<div class="ms-stat-row"><span class="k">inactive:</span><span class="v">{{ inactive }}</span></div>
|
||||
<div class="ms-stat-row"><span class="k">free:</span><span class="v">{{ free }}</span></div>
|
||||
<div class="ms-stat-row"><span class="k">buffers:</span><span class="v">{{ buffers }}</span></div>
|
||||
<div class="ms-stat-row"><span class="k">avail:</span><span class="v">{{ available }}</span></div>
|
||||
<div class="ms-stat-row"><span class="k">cached:</span><span class="v">{{ cached }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { store } from "../store.js";
|
||||
import Sparkline from "./sparkline.vue";
|
||||
|
||||
function colorFor(pct) {
|
||||
if (pct >= 90) return '#ff3355';
|
||||
if (pct >= 75) return '#ffcc00';
|
||||
if (pct >= 50) return '#4488ff';
|
||||
return '#00ff88';
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { Sparkline },
|
||||
props: { data: { type: Object } },
|
||||
computed: {
|
||||
memStats() { return this.data?.stats?.mem || {}; },
|
||||
memView() { return this.data?.views?.mem || {}; },
|
||||
swapStats() { return this.data?.stats?.memswap || {}; },
|
||||
swapView() { return this.data?.views?.memswap || {}; },
|
||||
history() { return store.history.mem; },
|
||||
|
||||
memPercent() { return (this.memStats.percent ?? 0).toFixed(1); },
|
||||
memDecoration() {
|
||||
if (!this.memView.percent) return 'ok';
|
||||
const d = this.memView.percent.decoration?.toLowerCase() || 'ok';
|
||||
return d.replace('_log', '');
|
||||
},
|
||||
|
||||
hasSwap() { return this.swapStats.percent != null && this.swapStats.percent > 0; },
|
||||
swapPercent() { return (this.swapStats.percent ?? 0).toFixed(1); },
|
||||
swapDecoration() {
|
||||
if (!this.swapView.percent) return 'ok';
|
||||
const d = this.swapView.percent.decoration?.toLowerCase() || 'ok';
|
||||
return d.replace('_log', '');
|
||||
},
|
||||
|
||||
sparkColor() { return colorFor(this.memStats.percent || 0); },
|
||||
|
||||
total() { return this.$filters.bytes(this.memStats.total); },
|
||||
used() { return this.$filters.bytes(this.memStats.used); },
|
||||
available() { return this.$filters.bytes(this.memStats.available); },
|
||||
free() { return this.$filters.bytes(this.memStats.free); },
|
||||
active() { return this.$filters.bytes(this.memStats.active); },
|
||||
inactive() { return this.$filters.bytes(this.memStats.inactive); },
|
||||
buffers() { return this.$filters.bytes(this.memStats.buffers); },
|
||||
cached() { return this.$filters.bytes(this.memStats.cached); },
|
||||
|
||||
usedDeco() {
|
||||
if (!this.memView.used) return '';
|
||||
const d = this.memView.used.decoration?.toLowerCase().replace('_log', '');
|
||||
if (d === 'warning') return 'warn';
|
||||
if (d === 'critical') return 'crit';
|
||||
if (d === 'ok') return 'ok';
|
||||
if (d === 'careful') return 'ok';
|
||||
return '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
377
glances/outputs/static/js/components/sidebar-section.vue
Normal file
377
glances/outputs/static/js/components/sidebar-section.vue
Normal file
@@ -0,0 +1,377 @@
|
||||
<template>
|
||||
<aside id="sidebar">
|
||||
|
||||
<!-- NETWORK -->
|
||||
<div class="sb-section" v-if="!args.disable_network && hasNetworks">
|
||||
<div class="sb-title">Network <span class="sub">Rx / Tx</span></div>
|
||||
<div class="sb-row" v-for="net in networks" :key="net.ifname">
|
||||
<span class="name">{{ net.alias || net.ifname }}</span>
|
||||
<span class="vals">
|
||||
<span class="v" :class="net.rxDeco">{{ net.rx }}</span>
|
||||
<span class="dim">/</span>
|
||||
<span class="v" :class="net.txDeco">{{ net.tx }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PORTS -->
|
||||
<div class="sb-section" v-if="!args.disable_ports && hasPorts">
|
||||
<template v-for="port in ports" :key="port.id">
|
||||
<div class="sb-row">
|
||||
<span class="name">{{ port.description || port.host }}</span>
|
||||
<span class="vals">
|
||||
<span class="v" :class="port.deco">{{ port.display }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- WIFI -->
|
||||
<div class="sb-section" v-if="!args.disable_wifi && hasWifi">
|
||||
<template v-for="ap in wifiList" :key="ap.ssid">
|
||||
<div class="sb-row">
|
||||
<span class="name" style="font-size:10px;color:var(--fg3)">{{ ap.ssid }}</span>
|
||||
<span class="vals">
|
||||
<span class="dim">{{ ap.signal }} dBm</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- DISK I/O -->
|
||||
<div class="sb-section" v-if="!args.disable_diskio && hasDisks">
|
||||
<div class="sb-title">Disk I/O <span class="sub">R / W</span></div>
|
||||
<div class="sb-row" v-for="disk in disks" :key="disk.name">
|
||||
<span class="name">{{ disk.alias || disk.name }}</span>
|
||||
<span class="vals">
|
||||
<span class="v" :class="disk.rDeco">{{ disk.read }}</span>
|
||||
<span class="dim">/</span>
|
||||
<span class="v" :class="disk.wDeco">{{ disk.write }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FILESYSTEM -->
|
||||
<div class="sb-section" v-if="!args.disable_fs && hasFs">
|
||||
<div class="sb-title">Filesystem <span class="sub">Used / Total</span></div>
|
||||
<div class="sb-row" v-for="fs in fileSystems" :key="fs.mnt">
|
||||
<span class="name">{{ fs.alias || fs.name }}</span>
|
||||
<span class="vals">
|
||||
<div class="sb-minibar">
|
||||
<div class="sb-minibar-fill" :class="fs.deco" :style="{ width: fs.percent + '%' }"></div>
|
||||
</div>
|
||||
<span class="v neutral">{{ fs.used }}/{{ fs.size }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOLDERS -->
|
||||
<div class="sb-section" v-if="hasFolders">
|
||||
<div class="sb-title">Folders <span class="sub">Size</span></div>
|
||||
<div class="sb-row" v-for="folder in folders" :key="folder.path">
|
||||
<span class="name" style="font-size:10px">{{ folder.path }}</span>
|
||||
<span class="vals">
|
||||
<span class="v" :class="folder.deco">{{ folder.size }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SENSORS -->
|
||||
<div class="sb-section" v-if="!args.disable_sensors && hasSensors">
|
||||
<div class="sb-title">Sensors</div>
|
||||
<div class="sb-row" v-for="sensor in sensors" :key="sensor.label">
|
||||
<span class="name">{{ sensor.label }}</span>
|
||||
<span class="vals">
|
||||
<template v-if="sensor.type === 'temperature_core' || sensor.type === 'temperature_hdd' || sensor.type === 'battery'">
|
||||
<div class="sb-minibar">
|
||||
<div class="sb-minibar-fill" :class="sensor.deco" :style="{ width: sensor.barPercent + '%' }"></div>
|
||||
</div>
|
||||
</template>
|
||||
<span class="v" :class="sensor.deco">{{ sensor.display }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONNECTIONS -->
|
||||
<div class="sb-section" v-if="!args.disable_connections && hasConnections" style="border-bottom:none">
|
||||
<div class="sb-title">Connections</div>
|
||||
<div class="sb-row" v-if="connections.listen != null">
|
||||
<span class="name">Listen</span>
|
||||
<span class="vals"><span class="v neutral">{{ connections.listen }}</span></span>
|
||||
</div>
|
||||
<div class="sb-row" v-if="connections.initiated != null">
|
||||
<span class="name">Initiated</span>
|
||||
<span class="vals"><span class="v neutral">{{ connections.initiated }}</span></span>
|
||||
</div>
|
||||
<div class="sb-row" v-if="connections.established != null">
|
||||
<span class="name">Established</span>
|
||||
<span class="vals"><span class="v neutral">{{ connections.established }}</span></span>
|
||||
</div>
|
||||
<div class="sb-row" v-if="connections.terminated != null">
|
||||
<span class="name">Terminated</span>
|
||||
<span class="vals"><span class="v neutral">{{ connections.terminated }}</span></span>
|
||||
</div>
|
||||
<div class="sb-row" v-if="connections.tracked != null">
|
||||
<span class="name">Tracked</span>
|
||||
<span class="vals"><span class="v" :class="connections.trackedDeco">{{ connections.tracked }}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { orderBy } from "lodash";
|
||||
import { store } from "../store.js";
|
||||
|
||||
export default {
|
||||
props: { data: { type: Object } },
|
||||
computed: {
|
||||
args() { return store.args || {}; },
|
||||
|
||||
// ── NETWORK ──
|
||||
hasNetworks() {
|
||||
const nets = this.data?.stats?.network;
|
||||
return nets && nets.length > 0;
|
||||
},
|
||||
networks() {
|
||||
const nets = this.data?.stats?.network || [];
|
||||
const views = this.data?.views?.network || {};
|
||||
const isByte = store.args?.byte;
|
||||
const isCumul = store.args?.network_cumul;
|
||||
const isSum = store.args?.network_sum;
|
||||
const fmt = this.$filters.bytes;
|
||||
const fmtBits = this.$filters.bits;
|
||||
|
||||
return nets
|
||||
.filter(n => !n.is_up || n.is_up !== false)
|
||||
.map(n => {
|
||||
const name = n.interface_name;
|
||||
let rx, tx, rxDeco = 'neutral', txDeco = 'neutral';
|
||||
|
||||
if (isSum) {
|
||||
const val = isCumul ? n.bytes_all : n.bytes_all_rate_per_sec;
|
||||
rx = isByte ? fmt(val) : fmtBits(val);
|
||||
tx = '';
|
||||
} else if (isCumul) {
|
||||
rx = isByte ? fmt(n.bytes_recv) : fmtBits(n.bytes_recv);
|
||||
tx = isByte ? fmt(n.bytes_sent) : fmtBits(n.bytes_sent);
|
||||
} else {
|
||||
rx = isByte ? fmt(n.bytes_recv_rate_per_sec) : fmtBits(n.bytes_recv_rate_per_sec);
|
||||
tx = isByte ? fmt(n.bytes_sent_rate_per_sec) : fmtBits(n.bytes_sent_rate_per_sec);
|
||||
}
|
||||
|
||||
if (views[name]) {
|
||||
if (views[name].bytes_recv_rate_per_sec?.decoration)
|
||||
rxDeco = views[name].bytes_recv_rate_per_sec.decoration.toLowerCase();
|
||||
if (views[name].bytes_sent_rate_per_sec?.decoration)
|
||||
txDeco = views[name].bytes_sent_rate_per_sec.decoration.toLowerCase();
|
||||
}
|
||||
|
||||
return { ifname: name, alias: n.alias, rx, tx, rxDeco, txDeco };
|
||||
});
|
||||
},
|
||||
|
||||
// ── PORTS ──
|
||||
hasPorts() {
|
||||
const p = this.data?.stats?.ports;
|
||||
return p && p.length > 0;
|
||||
},
|
||||
ports() {
|
||||
const ports = this.data?.stats?.ports || [];
|
||||
return ports.map((p, i) => {
|
||||
let display, deco;
|
||||
if (p.port != null) {
|
||||
// TCP port
|
||||
if (p.status === null) { deco = 'careful'; display = '?'; }
|
||||
else if (p.status === false) { deco = 'critical'; display = 'Timeout'; }
|
||||
else if (p.rtt_warning && p.status > p.rtt_warning) { deco = 'warning'; display = p.status.toFixed(0) + 'ms'; }
|
||||
else { deco = 'ok'; display = p.status.toFixed(0) + 'ms'; }
|
||||
} else if (p.url != null) {
|
||||
// Web URL
|
||||
if (p.status === null) { deco = 'careful'; display = '?'; }
|
||||
else if (![200, 301, 302].includes(p.status)) { deco = 'critical'; display = p.status; }
|
||||
else if (p.rtt_warning && p.elapsed > p.rtt_warning) { deco = 'warning'; display = p.elapsed?.toFixed(0) + 'ms'; }
|
||||
else { deco = 'ok'; display = p.status; }
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return { id: i, description: p.description || p.host || p.url, display, deco };
|
||||
}).filter(Boolean);
|
||||
},
|
||||
|
||||
// ── WIFI ──
|
||||
hasWifi() {
|
||||
const w = this.data?.stats?.wifi;
|
||||
return w && w.length > 0;
|
||||
},
|
||||
wifiList() {
|
||||
const w = this.data?.stats?.wifi || [];
|
||||
return w.filter(h => h.ssid).map(h => ({
|
||||
ssid: h.ssid,
|
||||
signal: h.quality_level,
|
||||
}));
|
||||
},
|
||||
|
||||
// ── DISK I/O ──
|
||||
hasDisks() {
|
||||
const d = this.data?.stats?.diskio;
|
||||
return d && d.length > 0;
|
||||
},
|
||||
disks() {
|
||||
const disks = this.data?.stats?.diskio || [];
|
||||
const views = this.data?.views?.diskio || {};
|
||||
const fmt = this.$filters.bytes;
|
||||
const isIops = store.args?.diskio_iops;
|
||||
const isLatency = store.args?.diskio_latency;
|
||||
|
||||
return disks.map(d => {
|
||||
const name = d.disk_name;
|
||||
let read, write;
|
||||
if (isLatency) {
|
||||
read = (d.read_latency || 0).toFixed(1) + 'ms';
|
||||
write = (d.write_latency || 0).toFixed(1) + 'ms';
|
||||
} else if (isIops) {
|
||||
read = Math.round(d.read_count_rate_per_sec || 0).toString();
|
||||
write = Math.round(d.write_count_rate_per_sec || 0).toString();
|
||||
} else {
|
||||
read = fmt(d.read_bytes_rate_per_sec);
|
||||
write = fmt(d.write_bytes_rate_per_sec);
|
||||
}
|
||||
|
||||
let rDeco = 'neutral', wDeco = 'neutral';
|
||||
if (views[name]) {
|
||||
if (views[name].read_bytes_rate_per_sec?.decoration)
|
||||
rDeco = views[name].read_bytes_rate_per_sec.decoration.toLowerCase();
|
||||
if (views[name].write_bytes_rate_per_sec?.decoration)
|
||||
wDeco = views[name].write_bytes_rate_per_sec.decoration.toLowerCase();
|
||||
}
|
||||
|
||||
return { name, alias: d.alias, read, write, rDeco, wDeco };
|
||||
});
|
||||
},
|
||||
|
||||
// ── FILESYSTEM ──
|
||||
hasFs() {
|
||||
const f = this.data?.stats?.fs;
|
||||
return f && f.length > 0;
|
||||
},
|
||||
fileSystems() {
|
||||
const fsList = this.data?.stats?.fs || [];
|
||||
const views = this.data?.views?.fs || {};
|
||||
const fmt = this.$filters.bytes;
|
||||
|
||||
return fsList.map(f => {
|
||||
const mnt = f.mnt_point;
|
||||
let deco = 'ok';
|
||||
if (views[mnt]?.percent?.decoration) {
|
||||
deco = views[mnt].percent.decoration.toLowerCase();
|
||||
}
|
||||
const alias = f.alias || (mnt === '/' ? 'Root' : mnt.split('/').pop() || mnt);
|
||||
return {
|
||||
mnt,
|
||||
name: alias,
|
||||
alias: f.alias,
|
||||
percent: f.percent || 0,
|
||||
used: fmt(f.used),
|
||||
size: fmt(f.size),
|
||||
deco,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// ── FOLDERS ──
|
||||
hasFolders() {
|
||||
const f = this.data?.stats?.folders;
|
||||
return f && f.length > 0;
|
||||
},
|
||||
folders() {
|
||||
const folders = this.data?.stats?.folders || [];
|
||||
const fmt = this.$filters.bytes;
|
||||
|
||||
return folders.map(f => {
|
||||
let deco = 'ok';
|
||||
if (f.errno && f.errno > 0) {
|
||||
deco = 'critical';
|
||||
} else if (f.critical && f.size > f.critical * 1000000) {
|
||||
deco = 'critical';
|
||||
} else if (f.warning && f.size > f.warning * 1000000) {
|
||||
deco = 'warning';
|
||||
} else if (f.careful && f.size > f.careful * 1000000) {
|
||||
deco = 'careful';
|
||||
}
|
||||
|
||||
return {
|
||||
path: f.path,
|
||||
size: f.errno && f.errno > 0 ? '? ' + f.errno : fmt(f.size),
|
||||
deco,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// ── SENSORS ──
|
||||
hasSensors() {
|
||||
const s = this.data?.stats?.sensors;
|
||||
return s && s.length > 0;
|
||||
},
|
||||
sensors() {
|
||||
const sensors = this.data?.stats?.sensors || [];
|
||||
const views = this.data?.views?.sensors || {};
|
||||
const isFahrenheit = store.args?.fahrenheit;
|
||||
|
||||
return sensors.map(s => {
|
||||
let value = s.value;
|
||||
let unit = s.unit || '';
|
||||
|
||||
if (isFahrenheit && (s.type === 'temperature_core' || s.type === 'temperature_hdd')) {
|
||||
value = value * 1.8 + 32;
|
||||
unit = '\u00b0F';
|
||||
}
|
||||
|
||||
let deco = 'neutral';
|
||||
if (views[s.label]?.value?.decoration) {
|
||||
deco = views[s.label].value.decoration.toLowerCase();
|
||||
}
|
||||
|
||||
let barPercent = 0;
|
||||
if (s.type === 'battery') {
|
||||
barPercent = value;
|
||||
} else if (s.type === 'temperature_core' || s.type === 'temperature_hdd') {
|
||||
barPercent = Math.min(100, (value / 100) * 100);
|
||||
}
|
||||
|
||||
const display = typeof value === 'number'
|
||||
? value.toFixed(0) + unit
|
||||
: value + unit;
|
||||
|
||||
return { label: s.label, type: s.type, display, deco, barPercent };
|
||||
});
|
||||
},
|
||||
|
||||
// ── CONNECTIONS ──
|
||||
hasConnections() {
|
||||
return this.data?.stats?.connections != null;
|
||||
},
|
||||
connections() {
|
||||
const c = this.data?.stats?.connections || {};
|
||||
const views = this.data?.views?.connections || {};
|
||||
let trackedDeco = 'neutral';
|
||||
if (views.nf_conntrack_percent?.decoration) {
|
||||
trackedDeco = views.nf_conntrack_percent.decoration.toLowerCase();
|
||||
}
|
||||
const tracked = c.nf_conntrack_count != null && c.nf_conntrack_max
|
||||
? `${c.nf_conntrack_count}/${c.nf_conntrack_max}`
|
||||
: null;
|
||||
|
||||
return {
|
||||
listen: c.LISTEN,
|
||||
initiated: c.initiated,
|
||||
established: c.ESTABLISHED,
|
||||
terminated: c.terminated,
|
||||
tracked,
|
||||
trackedDeco,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
94
glances/outputs/static/js/components/sparkline.vue
Normal file
94
glances/outputs/static/js/components/sparkline.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<canvas ref="canvas" :height="height" style="display:block;flex:1"></canvas>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: { type: Array, default: () => [] },
|
||||
max: { type: Number, default: 100 },
|
||||
height: { type: Number, default: 22 },
|
||||
color: { type: String, default: '#00ff88' },
|
||||
},
|
||||
mounted() {
|
||||
this.resizeObserver = new ResizeObserver(() => this.draw());
|
||||
this.resizeObserver.observe(this.$refs.canvas.parentElement);
|
||||
this.$nextTick(() => this.draw());
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.resizeObserver) this.resizeObserver.disconnect();
|
||||
},
|
||||
watch: {
|
||||
data: { handler() { this.draw(); }, deep: true },
|
||||
color() { this.draw(); },
|
||||
},
|
||||
methods: {
|
||||
draw() {
|
||||
const c = this.$refs.canvas;
|
||||
if (!c) return;
|
||||
const parent = c.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
// Size canvas to fill available flex space
|
||||
const siblings = [...parent.children].filter(el => el !== c);
|
||||
const gap = parseFloat(getComputedStyle(parent).gap) || 8;
|
||||
const usedW = siblings.reduce((s, el) => s + el.offsetWidth, 0);
|
||||
const w = Math.max(40, Math.floor(parent.offsetWidth - usedW - gap * siblings.length));
|
||||
c.width = w;
|
||||
|
||||
const ctx = c.getContext('2d');
|
||||
const W = c.width, H = c.height;
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
const pts = this.data.slice(-60);
|
||||
if (pts.length < 2) return;
|
||||
|
||||
const sx = W / (pts.length - 1);
|
||||
const pad = 2;
|
||||
const uh = H - pad * 2;
|
||||
const maxVal = this.max || 1;
|
||||
const tx = i => i * sx;
|
||||
const ty = v => pad + uh - Math.min(v, maxVal) / maxVal * uh;
|
||||
const color = this.color;
|
||||
|
||||
// gradient fill
|
||||
const g = ctx.createLinearGradient(0, 0, 0, H);
|
||||
g.addColorStop(0, color + '22');
|
||||
g.addColorStop(1, color + '03');
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tx(0), ty(pts[0]));
|
||||
for (let i = 1; i < pts.length; i++) ctx.lineTo(tx(i), ty(pts[i]));
|
||||
ctx.lineTo(tx(pts.length - 1), H);
|
||||
ctx.lineTo(0, H);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = g;
|
||||
ctx.fill();
|
||||
|
||||
// line
|
||||
ctx.save();
|
||||
ctx.shadowColor = color;
|
||||
ctx.shadowBlur = 5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tx(0), ty(pts[0]));
|
||||
for (let i = 1; i < pts.length; i++) ctx.lineTo(tx(i), ty(pts[i]));
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
// tip dot
|
||||
const lx = tx(pts.length - 1);
|
||||
const ly = ty(pts[pts.length - 1]);
|
||||
ctx.save();
|
||||
ctx.shadowColor = color;
|
||||
ctx.shadowBlur = 8;
|
||||
ctx.beginPath();
|
||||
ctx.arc(lx, ly, 2.5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
import Favico from "favico.js";
|
||||
import { store } from "./store.js";
|
||||
import { store, pushHistory } from "./store.js";
|
||||
|
||||
// prettier-ignore
|
||||
const fetchAll = () =>
|
||||
@@ -89,6 +89,7 @@ class GlancesStatsService {
|
||||
this.data = data;
|
||||
store.data = data;
|
||||
store.status = "SUCCESS";
|
||||
pushHistory(data.stats);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
|
||||
@@ -1,8 +1,48 @@
|
||||
import { reactive } from "vue";
|
||||
|
||||
const MAX_HISTORY = 120;
|
||||
|
||||
export const store = reactive({
|
||||
args: undefined,
|
||||
config: undefined,
|
||||
data: undefined,
|
||||
status: "IDLE",
|
||||
history: {
|
||||
cpu: [],
|
||||
mem: [],
|
||||
swap: [],
|
||||
load: [],
|
||||
gpu: [],
|
||||
},
|
||||
});
|
||||
|
||||
export function pushHistory(stats) {
|
||||
if (!stats) return;
|
||||
|
||||
if (stats.cpu != null && stats.cpu.total != null) {
|
||||
store.history.cpu.push(stats.cpu.total);
|
||||
if (store.history.cpu.length > MAX_HISTORY) store.history.cpu.shift();
|
||||
}
|
||||
|
||||
if (stats.mem != null && stats.mem.percent != null) {
|
||||
store.history.mem.push(stats.mem.percent);
|
||||
if (store.history.mem.length > MAX_HISTORY) store.history.mem.shift();
|
||||
}
|
||||
|
||||
if (stats.memswap != null && stats.memswap.percent != null) {
|
||||
store.history.swap.push(stats.memswap.percent);
|
||||
if (store.history.swap.length > MAX_HISTORY) store.history.swap.shift();
|
||||
}
|
||||
|
||||
if (stats.load != null && stats.load.min1 != null) {
|
||||
store.history.load.push(stats.load.min1);
|
||||
if (store.history.load.length > MAX_HISTORY) store.history.load.shift();
|
||||
}
|
||||
|
||||
if (stats.gpu != null && Array.isArray(stats.gpu) && stats.gpu.length > 0) {
|
||||
const meanProc =
|
||||
stats.gpu.reduce((s, g) => s + (g.proc || 0), 0) / stats.gpu.length;
|
||||
store.history.gpu.push(meanProc);
|
||||
if (store.history.gpu.length > MAX_HISTORY) store.history.gpu.shift();
|
||||
}
|
||||
}
|
||||
|
||||
BIN
glances/outputs/static/public/browser.js
vendored
BIN
glances/outputs/static/public/browser.js
vendored
Binary file not shown.
BIN
glances/outputs/static/public/glances.js
vendored
BIN
glances/outputs/static/public/glances.js
vendored
Binary file not shown.
@@ -1,12 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Glances</title>
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="static/favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet" />
|
||||
<script>
|
||||
window.__GLANCES__ = {
|
||||
'refresh-time': '{{ refresh_time }}'
|
||||
|
||||
Reference in New Issue
Block a user