Skip to content

Commit 6a47eca

Browse files
committed
Merge pull request louislam#700 from louislam/dockge
Merged @justwiebe's pull request that adds resource usage stats to the compose page. # Conflicts: # backend/agent-socket-handlers/docker-socket-handler.ts # backend/stack.ts # frontend/src/lang/en.json # frontend/src/pages/Compose.vue
2 parents b470fd9 + ba868b6 commit 6a47eca

File tree

9 files changed

+248
-8
lines changed

9 files changed

+248
-8
lines changed

backend/agent-socket-handlers/docker-socket-handler.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,21 @@ export class DockerSocketHandler extends AgentSocketHandler {
302302
}
303303
});
304304

305+
// Docker stats
306+
agentSocket.on("dockerStats", async (callback) => {
307+
try {
308+
checkLogin(socket);
309+
310+
const dockerStats = Object.fromEntries(await server.getDockerStats());
311+
callbackResult({
312+
ok: true,
313+
dockerStats,
314+
}, callback);
315+
} catch (e) {
316+
callbackError(e, callback);
317+
}
318+
});
319+
305320
// getExternalNetworkList
306321
agentSocket.on("getDockerNetworkList", async (callback) => {
307322
try {

backend/dockge-server.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,35 @@ export class DockgeServer {
637637
return list;
638638
}
639639

640+
async getDockerStats() : Promise<Map<string, object>> {
641+
let stats = new Map<string, object>();
642+
643+
try {
644+
let res = await childProcessAsync.spawn("docker", [ "stats", "--format", "json", "--no-stream" ], {
645+
encoding: "utf-8",
646+
});
647+
648+
if (!res.stdout) {
649+
return stats;
650+
}
651+
652+
let lines = res.stdout?.toString().split("\n");
653+
654+
for (let line of lines) {
655+
try {
656+
let obj = JSON.parse(line);
657+
stats.set(obj.Name, obj);
658+
} catch (e) {
659+
}
660+
}
661+
662+
return stats;
663+
} catch (e) {
664+
log.error("getDockerStats", e);
665+
return stats;
666+
}
667+
}
668+
640669
get stackDirFullPath() {
641670
return path.resolve(this.stacksDir);
642671
}

backend/stack.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ export class Stack {
529529
}
530530

531531
async getServiceStatusList() {
532-
let statusList = new Map<string, number>();
532+
let statusList = new Map<string, Array<object>>();
533533

534534
try {
535535
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
@@ -543,13 +543,23 @@ export class Stack {
543543

544544
let lines = res.stdout?.toString().split("\n");
545545

546+
const addLine = (obj: { Service: string, State: string, Name: string, Health: string }) => {
547+
if (!statusList.has(obj.Service)) {
548+
statusList.set(obj.Service, []);
549+
}
550+
statusList.get(obj.Service)?.push({
551+
status: obj.Health || obj.State,
552+
name: obj.Name
553+
});
554+
};
555+
546556
for (let line of lines) {
547557
try {
548558
let obj = JSON.parse(line);
549-
if (obj.Health === "") {
550-
statusList.set(obj.Service, obj.State);
559+
if (obj instanceof Array) {
560+
obj.forEach(addLine);
551561
} else {
552-
statusList.set(obj.Service, obj.Health);
562+
addLine(obj);
553563
}
554564
} catch (e) {
555565
}

frontend/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ declare module 'vue' {
1919
BModal: typeof import('bootstrap-vue-next')['BModal']
2020
Confirm: typeof import('./src/components/Confirm.vue')['default']
2121
Container: typeof import('./src/components/Container.vue')['default']
22+
DockerStat: typeof import('./src/components/DockerStat.vue')['default']
2223
General: typeof import('./src/components/settings/General.vue')['default']
2324
HiddenInput: typeof import('./src/components/HiddenInput.vue')['default']
2425
Login: typeof import('./src/components/Login.vue')['default']

frontend/src/components/Container.vue

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,32 @@
5656
{{ $t("deleteContainer") }}
5757
</button>
5858
</div>
59+
<div v-else-if="statsInstances.length > 0" class="mt-2">
60+
<div class="d-flex align-items-center gap-3">
61+
<template v-if="!expandedStats">
62+
<div class="stats">
63+
{{ $t('CPU') }}: {{ statsInstances[0].CPUPerc }}
64+
</div>
65+
<div class="stats">
66+
{{ $t('memoryAbbreviated') }}: {{ statsInstances[0].MemUsage }}
67+
</div>
68+
</template>
69+
<div class="d-flex flex-grow-1 justify-content-end">
70+
<button class="btn btn-sm btn-normal" @click="expandedStats = !expandedStats">
71+
<font-awesome-icon :icon="expandedStats ? 'chevron-up' : 'chevron-down'" />
72+
</button>
73+
</div>
74+
</div>
75+
<transition name="slide-fade" appear>
76+
<div v-if="expandedStats" class="d-flex flex-column gap-3 mt-2">
77+
<DockerStat
78+
v-for="stat in statsInstances"
79+
:key="stat.Name"
80+
:stat="stat"
81+
/>
82+
</div>
83+
</transition>
84+
</div>
5985

6086
<transition name="slide-fade" appear>
6187
<div v-if="isEditMode && showConfig" class="config mt-3">
@@ -159,10 +185,12 @@
159185
import { defineComponent } from "vue";
160186
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
161187
import { parseDockerPort } from "../../../common/util-common";
188+
import DockerStat from "./DockerStat.vue";
162189
163190
export default defineComponent({
164191
components: {
165192
FontAwesomeIcon,
193+
DockerStat
166194
},
167195
props: {
168196
name: {
@@ -177,16 +205,21 @@ export default defineComponent({
177205
type: Boolean,
178206
default: false,
179207
},
180-
status: {
181-
type: String,
182-
default: "N/A",
208+
serviceStatus: {
209+
type: Object,
210+
default: null,
211+
},
212+
dockerStats: {
213+
type: Object,
214+
default: null
183215
}
184216
},
185217
emits: [
186218
],
187219
data() {
188220
return {
189221
showConfig: false,
222+
expandedStats: false,
190223
};
191224
},
192225
computed: {
@@ -287,6 +320,22 @@ export default defineComponent({
287320
return "";
288321
}
289322
},
323+
statsInstances() {
324+
if (!this.serviceStatus) {
325+
return [];
326+
}
327+
328+
return this.serviceStatus
329+
.map(s => this.dockerStats[s.name])
330+
.filter(s => !!s)
331+
.sort((a, b) => a.Name.localeCompare(b.Name));
332+
},
333+
status() {
334+
if (!this.serviceStatus) {
335+
return "N/A";
336+
}
337+
return this.serviceStatus[0].status;
338+
}
290339
},
291340
mounted() {
292341
if (this.first) {
@@ -339,5 +388,10 @@ export default defineComponent({
339388
align-items: center;
340389
justify-content: end;
341390
}
391+
392+
.stats {
393+
font-size: 0.8rem;
394+
color: #6c757d;
395+
}
342396
}
343397
</style>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<template>
2+
<div class="stats-container">
3+
<div class="stats-title">
4+
{{ stat.Name }}
5+
</div>
6+
<div class="d-flex justify-content-between stats gap-2 mt-1">
7+
<div class="stat">
8+
<div class="stat-label">
9+
{{ $t('CPU') }}
10+
</div>
11+
<div>
12+
{{ stat.CPUPerc }}
13+
</div>
14+
</div>
15+
<div class="stat">
16+
<div class="stat-label">
17+
{{ $t('memory') }}
18+
</div>
19+
<div>
20+
{{ stat.MemUsage }} ({{ stat.MemPerc }})
21+
</div>
22+
</div>
23+
<div class="stat">
24+
<div class="stat-label">
25+
{{ $t('networkIO') }}
26+
</div>
27+
<div>
28+
{{ stat.NetIO }}
29+
</div>
30+
</div>
31+
<div class="stat">
32+
<div class="stat-label">
33+
{{ $t('blockIO') }}
34+
</div>
35+
<div>
36+
{{ stat.BlockIO }}
37+
</div>
38+
</div>
39+
</div>
40+
</div>
41+
</template>
42+
43+
<script>
44+
export default {
45+
props: {
46+
stat: {
47+
type: Object,
48+
required: true
49+
}
50+
},
51+
};
52+
</script>
53+
54+
<style lang="scss" scoped>
55+
.stats-container {
56+
container-type: inline-size;
57+
58+
.stats {
59+
container-type: inline-size;
60+
61+
.stat {
62+
display: flex;
63+
flex-direction: column;
64+
gap: 4px;
65+
}
66+
67+
@container (width < 420px) {
68+
flex-direction: column;
69+
70+
.stat {
71+
flex-direction: row;
72+
}
73+
74+
.stat-label::after {
75+
content: ':'
76+
}
77+
}
78+
}
79+
}
80+
81+
.stats {
82+
font-size: 0.8rem;
83+
color: #6c757d;
84+
}
85+
86+
.stat-label {
87+
font-weight: bold;
88+
}
89+
90+
.stats-title {
91+
font-size: 0.9rem;
92+
color: var(--bs-heading-color);
93+
}
94+
</style>

frontend/src/icon.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
faAward,
3939
faLink,
4040
faChevronDown,
41+
faChevronUp,
4142
faSignOutAlt,
4243
faPen,
4344
faExternalLinkSquareAlt,
@@ -90,6 +91,7 @@ library.add(
9091
faAward,
9192
faLink,
9293
faChevronDown,
94+
faChevronUp,
9395
faSignOutAlt,
9496
faPen,
9597
faExternalLinkSquareAlt,

frontend/src/lang/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,9 @@
138138
"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>" ,
139139
"ConsoleNotEnabledMSG3": "If you understand the risk, you can enable it by setting <code>DOCKGE_ENABLE_CONSOLE=true</code> in the environment variables.",
140140
"confirmLeaveStack": "You are currently editing a stack. Are you sure you want to leave?"
141+
"CPU": "CPU",
142+
"memory": "Memory",
143+
"memoryAbbreviated": "Mem",
144+
"networkIO": "Network I/O",
145+
"blockIO": "Block I/O"
141146
}

0 commit comments

Comments
 (0)