From 52de05cfd4d09ced8e39ed70654a7c0927b5613c Mon Sep 17 00:00:00 2001 From: Ross Mountjoy Date: Sat, 30 May 2020 09:12:56 -0400 Subject: [PATCH] (MANUALLY ADDING PR FROM Thlb 0.6) Add Docker platform and template app --- dashmachine/platform/docker.py | 449 ++++++++++++++++++++++ dashmachine/platform/lidarr.py | 59 --- dashmachine/static/images/apps/docker.png | Bin 0 -> 3121 bytes template_apps/Docker.ini | 7 + 4 files changed, 456 insertions(+), 59 deletions(-) create mode 100644 dashmachine/platform/docker.py create mode 100644 dashmachine/static/images/apps/docker.png create mode 100644 template_apps/Docker.ini diff --git a/dashmachine/platform/docker.py b/dashmachine/platform/docker.py new file mode 100644 index 0000000..db873b6 --- /dev/null +++ b/dashmachine/platform/docker.py @@ -0,0 +1,449 @@ +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 docs(self): + documentation = { + "name": "docker", + "author": "Thlb", + "author_url": "https://github.com/Thlb", + "version": 1.0, + "description": "Display information from Docker API. Informations can be displayed on a custom card or on an app card (e.g. Portainer App)", + "returns": "`value_template` as rendered string", + "returns_json_keys": [ + "version", + "max_api_version", + "name", + "containers", + "containers_running", + "containers_paused", + "containers_stopped", + "images", + "driver", + "cpu", + "memory", + "warnings", + "error (for debug)", + ], + "example": """ +```ini +# Working example (using un-encrypted connection, on Portainer card) +[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 +``` + """, + "variables": [ + { + "variable": "[variable_name]", + "description": "Name for the data source.", + "default": "None, entry is required", + "options": ".ini header", + }, + { + "variable": "platform", + "description": "Name of the platform.", + "default": "docker", + "options": "docker", + }, + { + "variable": "prefix", + "description": "The prefix for the app's url.", + "default": "", + "options": "web prefix, e.g. http:// or https://", + }, + { + "variable": "host", + "description": "Docker Host", + "default": "", + "options": "url,ip", + }, + { + "variable": "port", + "description": "Docker Port", + "default": "", + "options": "port", + }, + { + "variable": "api_version", + "description": "API version, by default platform will try to find latest version", + "default": "", + "options": "1.40", + }, + { + "variable": "tls_mode", + "description": "TLS verification mode", + "default": "None", + "options": "Server, Client, Both, None", + }, + { + "variable": "tls_ca", + "description": "Requiered for tls_mode=Both or tls_mode=Server", + "default": "None", + "options": "/path/to/ca, None", + }, + { + "variable": "tls_cert", + "description": "Requierd for tls_mode=Both or tls_mode=Client", + "default": "None", + "options": "/path/to/cert, None", + }, + { + "variable": "tls_key", + "description": "Requierd for tls_mode=Both or tls_mode=Client", + "default": "None", + "options": "/path/to/key, None", + }, + { + "variable": "card_type", + "description": "Set to Custom if you want to display informations in a custom card", + "default": "App", + "options": "Custom, App", + }, + { + "variable": "value_template", + "description": "Jinja template for how the returned data from API is displayed.", + "default": "", + "options": "jinja template", + }, + ], + } + return documentation + + 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/platform/lidarr.py b/dashmachine/platform/lidarr.py index 0c4ff45..8a97ddf 100644 --- a/dashmachine/platform/lidarr.py +++ b/dashmachine/platform/lidarr.py @@ -1,62 +1,3 @@ -""" -##### Lidarr -Display information from Lidarr API -```ini -[variable_name] -platform = lidarr -prefix = http:// -host = localhost -port = 8686 -api_key = my_api_key -verify = true -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. | lidarr | -| prefix | No | The prefix for the app's url. | web prefix, e.g. http:// or https:// | -| host | Yes | Lidarr Host | url,ip | -| port | No | Lidarr Port | port | -| api_key | Yes | ApiKey | api key | -| api_version | No | API version (default : v1) | v1 | -| verify | No | Turn TLS verification on or off, default is true | true,false | -| value_template | Yes | Jinja template for how the returned data from API is displayed. | jinja template | -
-###### **Available fields for value_template** -* version -* wanted_missing -* wanted_cutoff -* queue -* diskspace[x]['path'] -* diskspace[x]['total'] -* diskspace[x]['used'] -* diskspace[x]['free'] -* error (for debug) -> **Working example:** ->```ini -> [lidarr-data] -> platform = lidarr -> prefix = http:// -> host = 192.168.0.110 -> port = 8686 -> api_key = {{ API Key }} -> verify = False -> value_template = {{error}}Missing : {{wanted_missing}}
Queue : {{queue}}
Free ({{diskspace[0]['path']}}) : {{diskspace[0]['free']}} -> -> [Lidarr] -> prefix = http:// -> url = 192.168.0.110:8686 -> icon = static/images/apps/lidarr.png -> sidebar_icon = static/images/apps/lidarr.png -> description = Looks and smells like Sonarr but made for music -> open_in = this_tab -> data_sources = lidarr-data ->``` -""" - -import json from flask import render_template_string import requests diff --git a/dashmachine/static/images/apps/docker.png b/dashmachine/static/images/apps/docker.png new file mode 100644 index 0000000000000000000000000000000000000000..cea2f213e411211ef9b21f38d917274414e9ac61 GIT binary patch literal 3121 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