From a1a70d4a3c6a8314dd2ad35344d440e4ba8e86e8 Mon Sep 17 00:00:00 2001 From: Evan Steinkerchner <esteinkerchner@gmail.com> Date: Tue, 1 Mar 2022 20:55:45 -0500 Subject: [PATCH] Added Portainer custom service --- docs/customservices.md | 16 ++++ src/components/services/Portainer.vue | 122 ++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/components/services/Portainer.vue 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. url: "http://192.168.0.151/" # subtitle: "Monitor data server" ``` + +## Portainer + +This service displays info about the total number of containers managed by your Portainer instance. +In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass +it to the apikey field. + +See https://docs.portainer.io/v/ce-2.11/user/account-settings#access-tokens + +```yaml +- name: "Portainer" + logo: "assets/tools/sample.png" + url: "http://192.168.0.151/" + type: "Portainer" + apikey: "MY-SUPER-SECRET-API-KEY" +``` diff --git a/src/components/services/Portainer.vue b/src/components/services/Portainer.vue new file mode 100644 index 0000000..0af30eb --- /dev/null +++ b/src/components/services/Portainer.vue @@ -0,0 +1,122 @@ +<template> + <Generic :item="item"> + <template #indicator> + <div class="notifs"> + <strong v-if="running > 0" class="notif running" title="Running"> + {{ running }} + </strong> + <strong v-if="dead > 0" class="notif dead" title="Dead"> + {{ dead }} + </strong> + <strong v-if="misc > 0" class="notif misc" title="Other (creating, paused, exited, etc.)"> + {{ misc }} + </strong> + </div> + </template> + </Generic> +</template> + +<script> +import service from "@/mixins/service.js"; +import Generic from "./Generic.vue"; + +export default { + name: "Portainer", + mixins: [service], + props: { + item: Object, + }, + components: { + Generic, + }, + data: () => ({ + endpoints: null, + containers: null, + }), + computed: { + running: function () { + if (!this.containers) { + return ""; + } + return this.containers.filter((container) => { + return container.State.toLowerCase() === "running"; + }).length; + }, + dead: function () { + if (!this.containers) { + return ""; + } + return this.containers.filter((container) => { + return container.State.toLowerCase() === "dead"; + }).length || 1; + }, + misc: function () { + if (!this.containers) { + return ""; + } + return this.containers.filter((container) => { + return container.State.toLowerCase() !== "running" && container.State.toLowerCase() !== "dead"; + }).length; + }, + }, + created() { + this.fetchStatus(); + }, + methods: { + fetchStatus: async function () { + const headers = { + "X-Api-Key": this.item.apikey, + }; + + this.endpoints = await this.fetch("/api/endpoints", { headers }) + .catch((e) => { + console.error(e); + }); + + let containers = []; + for (let endpoint of this.endpoints) { + const uri = `/api/endpoints/${endpoint.Id}/docker/containers/json?all=1`; + const endpointContainers = await this.fetch(uri, { headers }) + .catch((e) => { + console.error(e); + }); + + containers = containers.concat(endpointContainers); + } + + this.containers = containers; + }, + }, +}; +</script> + +<style scoped lang="scss"> +.notifs { + position: absolute; + color: white; + font-family: sans-serif; + top: 0.3em; + right: 0.5em; + + .notif { + display: inline-block; + padding: 0.2em 0.35em; + border-radius: 0.25em; + position: relative; + margin-left: 0.3em; + font-size: 0.8em; + + &.running { + background-color: #4fd671; + } + + &.dead { + background-color: #e51111; + } + + &.misc { + background-color: #2ed0c8; + } + } +} +</style>