diff options
-rw-r--r-- | docs/customservices.md | 16 | ||||
-rw-r--r-- | src/components/services/Portainer.vue | 122 |
2 files changed, 138 insertions, 0 deletions
diff --git a/docs/customservices.md b/docs/customservices.md index 7e3e6b3..cb68e1d 100644 --- a/docs/customservices.md +++ b/docs/customservices.md | |||
@@ -125,3 +125,19 @@ For Prometheus you need to set the type to Prometheus and provide a url. | |||
125 | url: "http://192.168.0.151/" | 125 | url: "http://192.168.0.151/" |
126 | # subtitle: "Monitor data server" | 126 | # subtitle: "Monitor data server" |
127 | ``` | 127 | ``` |
128 | |||
129 | ## Portainer | ||
130 | |||
131 | This service displays info about the total number of containers managed by your Portainer instance. | ||
132 | In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass | ||
133 | it to the apikey field. | ||
134 | |||
135 | See https://docs.portainer.io/v/ce-2.11/user/account-settings#access-tokens | ||
136 | |||
137 | ```yaml | ||
138 | - name: "Portainer" | ||
139 | logo: "assets/tools/sample.png" | ||
140 | url: "http://192.168.0.151/" | ||
141 | type: "Portainer" | ||
142 | apikey: "MY-SUPER-SECRET-API-KEY" | ||
143 | ``` | ||
diff --git a/src/components/services/Portainer.vue b/src/components/services/Portainer.vue new file mode 100644 index 0000000..176d92e --- /dev/null +++ b/src/components/services/Portainer.vue | |||
@@ -0,0 +1,122 @@ | |||
1 | <template> | ||
2 | <Generic :item="item"> | ||
3 | <template #indicator> | ||
4 | <div class="notifs"> | ||
5 | <strong v-if="running > 0" class="notif running" title="Running"> | ||
6 | {{ running }} | ||
7 | </strong> | ||
8 | <strong v-if="dead > 0" class="notif dead" title="Dead"> | ||
9 | {{ dead }} | ||
10 | </strong> | ||
11 | <strong v-if="misc > 0" class="notif misc" title="Other (creating, paused, exited, etc.)"> | ||
12 | {{ misc }} | ||
13 | </strong> | ||
14 | </div> | ||
15 | </template> | ||
16 | </Generic> | ||
17 | </template> | ||
18 | |||
19 | <script> | ||
20 | import service from "@/mixins/service.js"; | ||
21 | import Generic from "./Generic.vue"; | ||
22 | |||
23 | export default { | ||
24 | name: "Portainer", | ||
25 | mixins: [service], | ||
26 | props: { | ||
27 | item: Object, | ||
28 | }, | ||
29 | components: { | ||
30 | Generic, | ||
31 | }, | ||
32 | data: () => ({ | ||
33 | endpoints: null, | ||
34 | containers: null, | ||
35 | }), | ||
36 | computed: { | ||
37 | running: function () { | ||
38 | if (!this.containers) { | ||
39 | return ""; | ||
40 | } | ||
41 | return this.containers.filter((container) => { | ||
42 | return container.State.toLowerCase() === "running"; | ||
43 | }).length; | ||
44 | }, | ||
45 | dead: function () { | ||
46 | if (!this.containers) { | ||
47 | return ""; | ||
48 | } | ||
49 | return this.containers.filter((container) => { | ||
50 | return container.State.toLowerCase() === "dead"; | ||
51 | }).length; | ||
52 | }, | ||
53 | misc: function () { | ||
54 | if (!this.containers) { | ||
55 | return ""; | ||
56 | } | ||
57 | return this.containers.filter((container) => { | ||
58 | return container.State.toLowerCase() !== "running" && container.State.toLowerCase() !== "dead"; | ||
59 | }).length; | ||
60 | }, | ||
61 | }, | ||
62 | created() { | ||
63 | this.fetchStatus(); | ||
64 | }, | ||
65 | methods: { | ||
66 | fetchStatus: async function () { | ||
67 | const headers = { | ||
68 | "X-Api-Key": this.item.apikey, | ||
69 | }; | ||
70 | |||
71 | this.endpoints = await this.fetch("/api/endpoints", { headers }) | ||
72 | .catch((e) => { | ||
73 | console.error(e); | ||
74 | }); | ||
75 | |||
76 | let containers = []; | ||
77 | for (let endpoint of this.endpoints) { | ||
78 | const uri = `/api/endpoints/${endpoint.Id}/docker/containers/json?all=1`; | ||
79 | const endpointContainers = await this.fetch(uri, { headers }) | ||
80 | .catch((e) => { | ||
81 | console.error(e); | ||
82 | }); | ||
83 | |||
84 | containers = containers.concat(endpointContainers); | ||
85 | } | ||
86 | |||
87 | this.containers = containers; | ||
88 | }, | ||
89 | }, | ||
90 | }; | ||
91 | </script> | ||
92 | |||
93 | <style scoped lang="scss"> | ||
94 | .notifs { | ||
95 | position: absolute; | ||
96 | color: white; | ||
97 | font-family: sans-serif; | ||
98 | top: 0.3em; | ||
99 | right: 0.5em; | ||
100 | |||
101 | .notif { | ||
102 | display: inline-block; | ||
103 | padding: 0.2em 0.35em; | ||
104 | border-radius: 0.25em; | ||
105 | position: relative; | ||
106 | margin-left: 0.3em; | ||
107 | font-size: 0.8em; | ||
108 | |||
109 | &.running { | ||
110 | background-color: #4fd671; | ||
111 | } | ||
112 | |||
113 | &.dead { | ||
114 | background-color: #e51111; | ||
115 | } | ||
116 | |||
117 | &.misc { | ||
118 | background-color: #2ed0c8; | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | </style> | ||