diff options
-rw-r--r-- | docs/customservices.md | 18 | ||||
-rw-r--r-- | src/components/services/Proxmox.vue | 101 |
2 files changed, 119 insertions, 0 deletions
diff --git a/docs/customservices.md b/docs/customservices.md index df97cc9..67baed0 100644 --- a/docs/customservices.md +++ b/docs/customservices.md | |||
@@ -21,6 +21,7 @@ within Homer: | |||
21 | + [Tautulli](#tautulli) | 21 | + [Tautulli](#tautulli) |
22 | + [Mealie](#mealie) | 22 | + [Mealie](#mealie) |
23 | + [Healthchecks](#healthchecks) | 23 | + [Healthchecks](#healthchecks) |
24 | + [Proxmox](#proxmox) | ||
24 | 25 | ||
25 | If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page. | 26 | If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page. |
26 | 27 | ||
@@ -244,3 +245,20 @@ Two lines are needed in the config.yml : | |||
244 | 245 | ||
245 | The url must be the root url of the Healthchecks application. | 246 | The url must be the root url of the Healthchecks application. |
246 | The Healthchecks API key can be found in Settings > API Access > API key (read-only). The key is needed to access Healthchecks API. | 247 | The Healthchecks API key can be found in Settings > API Access > API key (read-only). The key is needed to access Healthchecks API. |
248 | |||
249 | ## Proxmox | ||
250 | |||
251 | This service displays status information of a Proxmox node (VMs running and disk, memory and cpu used). It uses the proxmox API and [API Tokens](https://pve.proxmox.com/pve-docs/pveum-plain.html) for authorization so you need to generate one to set in the yaml config. You can set it up in Proxmox under Permissions > API Tokens. You also need to know the realm the user of the API Token is assigned to (by default pam). | ||
252 | |||
253 | Configuration example: | ||
254 | |||
255 | ```yaml | ||
256 | - name: "Proxmox - Node" | ||
257 | logo: "https://www.google.com/url?sa=i&url=https%3A%2F%2Fgithub.com%2FandOTP%2FandOTP%2Fissues%2F337&psig=AOvVaw2YKVuEUIBeTUikr7kAjm8D&ust=1665323538747000&source=images&cd=vfe&ved=0CAkQjRxqFwoTCPCTruLj0PoCFQAAAAAdAAAAABAN" | ||
258 | type: "Proxmox" | ||
259 | url: "https://your.proxmox.server" | ||
260 | node: "your-node-name" | ||
261 | warning_value: 50 | ||
262 | danger_value: 80 | ||
263 | api_token: "PVEAPIToken=root@pam!your-api-token-name=your-api-token-key" | ||
264 | ``` \ No newline at end of file | ||
diff --git a/src/components/services/Proxmox.vue b/src/components/services/Proxmox.vue new file mode 100644 index 0000000..38d09d3 --- /dev/null +++ b/src/components/services/Proxmox.vue | |||
@@ -0,0 +1,101 @@ | |||
1 | <template> | ||
2 | <Generic :item="item"> | ||
3 | <template #content> | ||
4 | <p class="title is-4">{{ item.name }}</p> | ||
5 | <p class="subtitle is-6"> | ||
6 | <template v-if="item.subtitle"> | ||
7 | {{ item.subtitle }} | ||
8 | </template> | ||
9 | <template v-else-if="vms"> | ||
10 | <div v-if="error"> | ||
11 | <strong class="danger">Error loading info!</strong> | ||
12 | </div> | ||
13 | <div v-else class="metrics"> | ||
14 | <span>VMs: <strong class="is-number">{{ vms.running }}</strong></span> | ||
15 | <span>Disk: <strong class="is-number" :class="statusClass(diskUsed)">{{ diskUsed }}%</strong></span> | ||
16 | <span>Mem: <strong class="is-number" :class="statusClass(memoryUsed)">{{ memoryUsed }}%</strong></span> | ||
17 | <span>CPU: <strong class="is-number" :class="statusClass(cpuUsed)">{{ cpuUsed }}%</strong></span> | ||
18 | </div> | ||
19 | </template> | ||
20 | </p> | ||
21 | </template> | ||
22 | </Generic> | ||
23 | </template> | ||
24 | |||
25 | <script> | ||
26 | import service from "@/mixins/service.js"; | ||
27 | import Generic from "./Generic.vue"; | ||
28 | |||
29 | export default { | ||
30 | name: "Proxmox", | ||
31 | mixins: [service], | ||
32 | props: { | ||
33 | item: Object, | ||
34 | }, | ||
35 | components: { | ||
36 | Generic, | ||
37 | }, | ||
38 | data: () => ({ | ||
39 | vms: { | ||
40 | total: 0, | ||
41 | running: 0 | ||
42 | }, | ||
43 | memoryUsed: 0, | ||
44 | diskUsed: 0, | ||
45 | cpuUsed: 0, | ||
46 | error: false | ||
47 | }), | ||
48 | created() { | ||
49 | this.fetchStatus(); | ||
50 | }, | ||
51 | methods: { | ||
52 | statusClass(value) { | ||
53 | if (value > this.item.danger_value) return 'danger'; | ||
54 | if (value > this.item.warning_value) return 'warning'; | ||
55 | return 'healthy' | ||
56 | }, | ||
57 | fetchStatus: async function () { | ||
58 | try { | ||
59 | const options = { | ||
60 | headers: { | ||
61 | "Authorization": this.item.api_token | ||
62 | } | ||
63 | } | ||
64 | const status = await this.fetch(`/api2/json/nodes/${this.item.node}/status`, options); | ||
65 | // main metrics: | ||
66 | this.memoryUsed = ( (status.data.memory.used * 100) / status.data.memory.total ).toFixed(1); | ||
67 | this.diskUsed = ( (status.data.rootfs.used * 100) / status.data.rootfs.total ).toFixed(1); | ||
68 | this.cpuUsed = (status.data.cpu * 100).toFixed(1); | ||
69 | // vms: | ||
70 | const vms = await this.fetch(`/api2/json/nodes/${this.item.node}/qemu`, options); | ||
71 | this.vms.total += vms.data.length; | ||
72 | this.vms.running += vms.data.filter( i => i.status === 'running' ).length; | ||
73 | this.error = false; | ||
74 | } catch(err) { | ||
75 | console.log(err); | ||
76 | this.error = true; | ||
77 | } | ||
78 | }, | ||
79 | }, | ||
80 | }; | ||
81 | </script> | ||
82 | |||
83 | <style scoped lang="scss"> | ||
84 | .is-number { | ||
85 | font-family: "Lato" | ||
86 | } | ||
87 | .healthy { | ||
88 | color: green | ||
89 | } | ||
90 | .warning { | ||
91 | color: orange | ||
92 | } | ||
93 | .danger { | ||
94 | color: red | ||
95 | } | ||
96 | .metrics { | ||
97 | display: flex; | ||
98 | justify-content: space-between; | ||
99 | } | ||
100 | </style> | ||
101 | \ No newline at end of file | ||