mirror of
https://github.com/louislam/dockge.git
synced 2026-05-21 14:02:17 +00:00
Add resource usage stats to the compose page (#700)
Co-authored-by: cmcooper1980 <31871143+cmcooper1980@users.noreply.github.com>
This commit is contained in:
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -19,6 +19,7 @@ declare module 'vue' {
|
||||
BModal: typeof import('bootstrap-vue-next')['BModal']
|
||||
Confirm: typeof import('./src/components/Confirm.vue')['default']
|
||||
Container: typeof import('./src/components/Container.vue')['default']
|
||||
DockerStat: typeof import('./src/components/DockerStat.vue')['default']
|
||||
General: typeof import('./src/components/settings/General.vue')['default']
|
||||
GlobalEnv: typeof import('./src/components/settings/GlobalEnv.vue')['default']
|
||||
HiddenInput: typeof import('./src/components/HiddenInput.vue')['default']
|
||||
|
||||
@@ -58,6 +58,32 @@
|
||||
{{ $t("deleteContainer") }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="statsInstances.length > 0" class="mt-2">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<template v-if="!expandedStats">
|
||||
<div class="stats">
|
||||
{{ $t('CPU') }}: {{ statsInstances[0].CPUPerc }}
|
||||
</div>
|
||||
<div class="stats">
|
||||
{{ $t('memoryAbbreviated') }}: {{ statsInstances[0].MemUsage }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="d-flex flex-grow-1 justify-content-end">
|
||||
<button class="btn btn-sm btn-normal" @click="expandedStats = !expandedStats">
|
||||
<font-awesome-icon :icon="expandedStats ? 'chevron-up' : 'chevron-down'" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<transition name="slide-fade" appear>
|
||||
<div v-if="expandedStats" class="d-flex flex-column gap-3 mt-2">
|
||||
<DockerStat
|
||||
v-for="stat in statsInstances"
|
||||
:key="stat.Name"
|
||||
:stat="stat"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<transition name="slide-fade" appear>
|
||||
<div v-if="isEditMode && showConfig" class="config mt-3">
|
||||
@@ -161,10 +187,12 @@
|
||||
import { defineComponent } from "vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { parseDockerPort } from "../../../common/util-common";
|
||||
import DockerStat from "./DockerStat.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FontAwesomeIcon,
|
||||
DockerStat
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
@@ -179,16 +207,12 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: "N/A",
|
||||
serviceStatus: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
processing: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ports: {
|
||||
type: Array,
|
||||
dockerStats: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
@@ -200,6 +224,7 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
showConfig: false,
|
||||
expandedStats: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -304,6 +329,22 @@ export default defineComponent({
|
||||
return "";
|
||||
}
|
||||
},
|
||||
statsInstances() {
|
||||
if (!this.serviceStatus) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.serviceStatus
|
||||
.map(s => this.dockerStats[s.name])
|
||||
.filter(s => !!s)
|
||||
.sort((a, b) => a.Name.localeCompare(b.Name));
|
||||
},
|
||||
status() {
|
||||
if (!this.serviceStatus) {
|
||||
return "N/A";
|
||||
}
|
||||
return this.serviceStatus[0].status;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.first) {
|
||||
@@ -356,5 +397,10 @@ export default defineComponent({
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.stats {
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
94
frontend/src/components/DockerStat.vue
Normal file
94
frontend/src/components/DockerStat.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="stats-container">
|
||||
<div class="stats-title">
|
||||
{{ stat.Name }}
|
||||
</div>
|
||||
<div class="d-flex justify-content-between stats gap-2 mt-1">
|
||||
<div class="stat">
|
||||
<div class="stat-label">
|
||||
{{ $t('CPU') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ stat.CPUPerc }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">
|
||||
{{ $t('memory') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ stat.MemUsage }} ({{ stat.MemPerc }})
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">
|
||||
{{ $t('networkIO') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ stat.NetIO }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">
|
||||
{{ $t('blockIO') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ stat.BlockIO }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
stat: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.stats-container {
|
||||
container-type: inline-size;
|
||||
|
||||
.stats {
|
||||
container-type: inline-size;
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@container (width < 420px) {
|
||||
flex-direction: column;
|
||||
|
||||
.stat {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.stat-label::after {
|
||||
content: ':'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats {
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stats-title {
|
||||
font-size: 0.9rem;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
</style>
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
faAward,
|
||||
faLink,
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faSignOutAlt,
|
||||
faPen,
|
||||
faExternalLinkSquareAlt,
|
||||
@@ -90,6 +91,7 @@ library.add(
|
||||
faAward,
|
||||
faLink,
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faSignOutAlt,
|
||||
faPen,
|
||||
faExternalLinkSquareAlt,
|
||||
|
||||
@@ -133,6 +133,11 @@
|
||||
"Network name...": "Network name...",
|
||||
"Select a network...": "Select a network...",
|
||||
"NoNetworksAvailable": "No networks available. You need to add internal networks or enable external networks in the right side first.",
|
||||
"CPU": "CPU",
|
||||
"memory": "Memory",
|
||||
"memoryAbbreviated": "Mem",
|
||||
"networkIO": "Network I/O",
|
||||
"blockIO": "Block I/O",
|
||||
"Console is not enabled": "Console is not enabled",
|
||||
"ConsoleNotEnabledMSG1": "Console is a powerful tool that allows you to execute any commands such as <code>docker</code>, <code>rm</code> within the Dockge's container in this Web UI.",
|
||||
"ConsoleNotEnabledMSG2": "It might be dangerous since this Dockge container is connecting to the host's Docker daemon. Also Dockge could be possibly taken down by commands like <code>rm -rf</code>" ,
|
||||
|
||||
@@ -128,12 +128,11 @@
|
||||
:name="name"
|
||||
:is-edit-mode="isEditMode"
|
||||
:first="name === Object.keys(jsonConfig.services)[0]"
|
||||
:processing="processing"
|
||||
:serviceStatus="serviceStatusList[name]"
|
||||
:dockerStats="dockerStats"
|
||||
@start-service="startService"
|
||||
@stop-service="stopService"
|
||||
@restart-service="restartService"
|
||||
:status="serviceStatusList[name]?.state"
|
||||
:ports="serviceStatusList[name]?.ports"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -282,6 +281,12 @@ const envDefault = "# VARIABLE=value #comment";
|
||||
let yamlErrorTimeout = null;
|
||||
|
||||
let serviceStatusTimeout = null;
|
||||
let dockerStatsTimeout = null;
|
||||
let prismjsSymbolDefinition = {
|
||||
"symbol": {
|
||||
pattern: /(?<!\$)\$(\{[^{}]*\}|\w+)/,
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -337,11 +342,13 @@ export default {
|
||||
|
||||
},
|
||||
serviceStatusList: {},
|
||||
dockerStats: {},
|
||||
isEditMode: false,
|
||||
submitted: false,
|
||||
showDeleteDialog: false,
|
||||
newContainerName: "",
|
||||
stopServiceStatusTimeout: false,
|
||||
stopDockerStatsTimeout: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -508,6 +515,7 @@ export default {
|
||||
}
|
||||
|
||||
this.requestServiceStatus();
|
||||
this.requestDockerStats();
|
||||
},
|
||||
unmounted() {
|
||||
|
||||
@@ -520,6 +528,13 @@ export default {
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
startDockerStatsTimeout() {
|
||||
clearTimeout(dockerStatsTimeout);
|
||||
dockerStatsTimeout = setTimeout(async () => {
|
||||
this.requestDockerStats();
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
requestServiceStatus() {
|
||||
// Do not request if it is add mode
|
||||
if (this.isAdd) {
|
||||
@@ -536,6 +551,17 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
requestDockerStats() {
|
||||
this.$root.emitAgent(this.endpoint, "dockerStats", (res) => {
|
||||
if (res.ok) {
|
||||
this.dockerStats = res.dockerStats;
|
||||
}
|
||||
if (!this.stopDockerStatsTimeout) {
|
||||
this.startDockerStatsTimeout();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
exitConfirm(next) {
|
||||
if (this.isEditMode) {
|
||||
if (confirm(this.$t("confirmLeaveStack"))) {
|
||||
@@ -553,7 +579,9 @@ export default {
|
||||
exitAction() {
|
||||
console.log("exitAction");
|
||||
this.stopServiceStatusTimeout = true;
|
||||
this.stopDockerStatsTimeout = true;
|
||||
clearTimeout(serviceStatusTimeout);
|
||||
clearTimeout(dockerStatsTimeout);
|
||||
|
||||
// Leave Combined Terminal
|
||||
console.debug("leaveCombinedTerminal", this.endpoint, this.stack.name);
|
||||
|
||||
Reference in New Issue
Block a user