diff --git a/dashmachine/platform/docker.py b/dashmachine/platform/docker.py new file mode 100644 index 0000000..4e3dfd1 --- /dev/null +++ b/dashmachine/platform/docker.py @@ -0,0 +1,394 @@ +""" +##### Docker +Display information from Docker API. Informations can be displayed on a custom card or on an app card (e.g. Portainer App) +```ini +[variable_name] +platform = docker +prefix = http:// +host = localhost +port = 2375 +value_template = {{ value_template }} +``` +> **Returns:** `value_template` as rendered string +| Variable | Required | Description | Options | +|-----------------|----------|-----------------------------------------------------------------|-------------------| +| [variable_name] | Yes | Name for the data source. | [variable_name] | +| platform | Yes | Name of the platform. | docker | +| prefix | No | The prefix for the app's url. | web prefix, e.g. http:// or https:// | +| host | Yes | Docker Host | url,ip | +| port | No | Docker Port | port, usually 2375 (Insecure) or 2376 (TLS) | +| api_version | No | Docker API version to use (Default : platform will try to find latest version) | 1.40 | +| tls_mode | No | TLS verification mode, default is None | Server, Client, Both, None | +| tls_ca | No | Requierd for tls_mode=Both or tls_mode=Server, default is None | /path/to/ca, None | +| tls_cert | No | Requierd for tls_mode=Both or tls_mode=Client, default is None | /path/to/cert, None | +| tls_key | No | Requierd for tls_mode=Both or tls_mode=Client, default is None | /path/to/key, None| +| card_type | No | Set to Custom if you want to display informations in a custom card. Default is App | Custom, App| +| value_template | Yes | Jinja template for how the returned data from API is displayed. | jinja template | +
+###### **Available fields for value_template** +* version +* max_api_version +* name +* containers +* containers_running +* containers_paused +* containers_stopped +* images +* driver +* cpu +* memory +* warnings +* error (for debug) +> **Working example (using un-encrypted connection, on Portainer card):** +>```ini +> [docker-endpoint-1] +> platform = docker +> prefix = http:// +> host = 192.168.0.110 +> port = 2375 +> value_template = {{error}}

{{name}}
fiber_manual_record{{containers_running}}fiber_manual_record{{containers_paused}}fiber_manual_record{{containers_stopped}}

+> +> [Portainer] +> prefix = http:// +> url = 192.168.0.110:2375 +> icon = static/images/apps/portainer.png +> sidebar_icon = static/images/apps/portainer.png +> description = Making Docker management easy +> open_in = this_tab +> data_sources = docker-endpoint-1 +>``` +> +> +> **Working example (using encrypted connection, on Portainer card):** +>```ini +> [docker-endpoint-2] +> platform = docker +> prefix = https:// +> host = 192.168.0.110 +> port = 2376 +> tls_mode = Both +> tls_ca = /path/to/ca_file +> tls_cert = /path/to/cert_file +> tls_key = /path/to/key_file +> value_template = {{error}}

{{name}}
fiber_manual_record{{containers_running}}fiber_manual_record{{containers_paused}}fiber_manual_record{{containers_stopped}}

+> +> [Portainer] +> prefix = http:// +> url = 192.168.0.110:2375 +> icon = static/images/apps/portainer.png +> sidebar_icon = static/images/apps/portainer.png +> description = Making Docker management easy +> open_in = this_tab +> data_sources = docker-endpoint-2 +>``` +> +> +> **Working example (using un-encrypted connection, on custom Docker card):** +>```ini +> [docker-endpoint-3] +> platform = docker +> prefix = http:// +> host = 192.168.0.110 +> port = 2375 +> card_type = Custom +> +> [Docker] +> type = custom +> data_sources = docker-endpoint-3 +>``` +""" + +import json +from flask import render_template_string +import requests +import re + + +class Docker(object): + def __init__( + self, + method, + prefix, + host, + port, + api_version, + card_type, + tls_mode, + tls_ca, + tls_cert, + tls_key, + ): + self.endpoint = None + self.method = method + self.prefix = prefix + self.host = host + self.port = port + self.api_version = api_version + self.card_type = card_type + self.tls_mode = tls_mode + self.tls_ca = tls_ca + self.tls_key = tls_key + self.tls_cert = tls_cert + + # Initialize results + self.error = None + self.version = "?" + self.max_api_version = "?" + self.name = "?" + self.running = 0 + self.paused = 0 + self.stopped = 0 + self.images = 0 + self.driver = "?" + self.cpu = "?" + self.memory = "?" + self.html_template = "" + + def check(self): + port = "" if self.port == None else ":" + self.port + + if self.method.upper() == "GET": + try: + response = "" + request = requests.get( + self.prefix + self.host + port + "/v999/info", + verify=self.tls_ca, + cert=(self.tls_cert, self.tls_key), + timeout=10, + ) + response = request.text + if "text/plain" in request.headers["content-type"]: + self.error = request.text + rawdata = None + elif "application/json" in request.headers["content-type"]: + rawdata = request.json() + else: + error = request + rawdata = None + + except Exception as e: + rawdata = None + self.error = f"{e}" + " " + response + self.setHtml() + + if rawdata != None: + if "message" in rawdata: + regex = r"\bv?[0-9]+\.[0-9]+(?:\.[0-9]+)?\b" + r = re.search(regex, rawdata["message"]) + self.max_api_version = r.group(0) + self.api_version = ( + self.api_version + if self.api_version != None + else self.max_api_version + ) + self.endpoint = "/v" + self.api_version + "/" + + def getStatus(self): + port = "" if self.port == None else ":" + self.port + + if self.method.upper() == "GET": + try: + rawdata = requests.get( + self.prefix + self.host + port + self.endpoint + "/info", + verify=self.tls_ca, + cert=(self.tls_cert, self.tls_key), + timeout=10, + ).json() + except Exception as e: + rawdata = None + self.error = f"{e}" + self.setHtml() + + if rawdata != None: + self.name = rawdata["Name"] + self.containers = rawdata["Containers"] + self.containers_running = rawdata["ContainersRunning"] + self.containers_paused = rawdata["ContainersPaused"] + self.containers_stopped = rawdata["ContainersStopped"] + self.images = rawdata["Images"] + self.warnings = rawdata["Warnings"] + self.driver = rawdata["Driver"] + self.cpu = rawdata["NCPU"] + self.memory = self.formatSize(rawdata["MemTotal"]) + if self.card_type == "Custom": + self.setHtml() + + def formatSize(self, size): + # 2**10 = 1024 + power = 2 ** 10 + n = 0 + power_labels = {0: "", 1: "KB", 2: "MB", 3: "GB", 4: "TB"} + while size > power: + size /= power + n += 1 + return str(round(size, 1)) + " " + power_labels[n] + + def refresh(self): + self.check() + if self.error == None: + self.error = "" + self.getStatus() + + def setHtml(self): + if self.error != None and self.error != "": + self.html_template = """ +
+
+ error +
+
+ Docker +
+
+
+
Error
+
+
+ keyboard_arrow_down +
+
+
+
+
{{ error }}
+
+
+
+ """ + else: + if self.tls_mode == None: + img_tls = """ + lock_open + """ + else: + img_tls = """ + lock + """ + if len(self.warnings) > 0: + img_warnings = """ + warning + """ + else: + img_warnings = """ + warning + """ + self.html_template = ( + """ +
+
+ """ + + img_tls + + img_warnings + + """ +
+
+ Docker +
+
+
+
{{name}}
+
+
+ keyboard_arrow_down +
+
+
+
+
Containers: {{ containers }}
+
Running: {{ containers_running }}
+
Paused: {{ containers_paused }}
+
Stopped: {{ containers_stopped }}
+
Images: {{ images }}
+
Driver: {{ driver }}
+
CPU: {{ cpu }}
+
Memory: {{ memory }}
+
+
+
+ """ + ) + + def getHtml(self): + return self.html_template + + +class Platform: + def __init__(self, *args, **kwargs): + # parse the user's options from the config entries + for key, value in kwargs.items(): + self.__dict__[key] = value + + # set defaults for omitted options + if not hasattr(self, "method"): + self.method = "GET" + if not hasattr(self, "prefix"): + self.prefix = "http://" + if not hasattr(self, "host"): + self.host = None + if not hasattr(self, "port"): + self.port = 2375 + if not hasattr(self, "api_version"): + self.api_version = None + if not hasattr(self, "card_type"): + self.card_type = "App" + if not hasattr(self, "tls_ca"): + self.tls_ca = None + if not hasattr(self, "tls_cert"): + self.tls_cert = None + if not hasattr(self, "tls_key"): + self.tls_key = None + # Without TLS + if not hasattr(self, "tls_mode"): + self.tls_mode = None + self.tls_ca = None + self.tls_cert = None + self.tls_key = None + else: + if self.tls_mode == "Both": + if self.tls_ca == None or self.tls_cert == None or self.tls_key == None: + return "tls_mode set to Both, and missing tls_ca/tls_cert/tls_key" + elif self.tls_mode == "Client": + self.tls_ca = False + elif self.tls_mode == "Server": + self.tls_cert = "" + self.tls_key = "" + elif self.tls_mode == "None": + self.tls_ca = None + self.tls_cert = None + self.tls_key = None + + self.docker = Docker( + self.method, + self.prefix, + self.host, + self.port, + self.api_version, + self.card_type, + self.tls_mode, + self.tls_ca, + self.tls_cert, + self.tls_key, + ) + + def process(self): + if self.host == None: + return "host missing" + # TLS check + if self.tls_mode == "Both": + if self.tls_ca == None or self.tls_cert == None or self.tls_key == None: + return "tls_mode set to Both, and missing tls_ca/tls_cert/tls_key" + elif self.tls_mode == "Client": + if self.tls_cert == None or self.tls_key == None: + return "tls_mode set to Client, and missing tls_cert/tls_key" + elif self.tls_mode == "Server": + if self.tls_ca == None: + return "tls_mode set to Server, and missing tls_ca" + else: + if self.tls_mode != None: + return "Invalid tls_mode : " + self.tls_mode + + self.docker.refresh() + + if self.card_type == "Custom": + return render_template_string(self.docker.getHtml(), **self.docker.__dict__) + else: + return render_template_string(self.value_template, **self.docker.__dict__) diff --git a/dashmachine/static/images/apps/docker.png b/dashmachine/static/images/apps/docker.png new file mode 100644 index 0000000..9b23d4d Binary files /dev/null and b/dashmachine/static/images/apps/docker.png differ diff --git a/template_apps/Docker.ini b/template_apps/Docker.ini new file mode 100644 index 0000000..de77274 --- /dev/null +++ b/template_apps/Docker.ini @@ -0,0 +1,7 @@ +[Docker] +prefix = http:// +url = your-website.com +icon = static/images/apps/docker.png +sidebar_icon = static/images/apps/docker.png +description = Empowering App Development for Developers +open_in = this_tab \ No newline at end of file