450 lines
17 KiB
Python
450 lines
17 KiB
Python
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 = """
|
|
<div class="row">
|
|
<div class="col s6">
|
|
<span class="mt-0 mb-0 theme-primary-text font-weight-700" style="font-size: 36px"><i class="material-icons md-18 theme-failure-text" title="Error">error</i></h3>
|
|
</div>
|
|
<div class="col s6 right-align">
|
|
<img height="48px" src="static/images/apps/docker.png" alt="Docker">
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<h6 class="font-weight-900 center theme-muted-text">Error</h6>
|
|
</div>
|
|
<div class="row center-align">
|
|
<i class="material-icons-outlined">keyboard_arrow_down</i>
|
|
</div>
|
|
<div class="row center-align">
|
|
<div class="col s12">
|
|
<div class="collection theme-muted-text">
|
|
<div class="collection-item">{{ error }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
else:
|
|
if self.tls_mode == None:
|
|
img_tls = """
|
|
<i class="material-icons md-18 theme-warning-text" title="TLS disabled">lock_open</i>
|
|
"""
|
|
else:
|
|
img_tls = """
|
|
<i class="material-icons md-18 theme-success-text" title="TLS enabled">lock</i>
|
|
"""
|
|
if len(self.warnings) > 0:
|
|
img_warnings = """
|
|
<i class="material-icons md-18 theme-warning-text" title="{{warnings}}">warning</i>
|
|
"""
|
|
else:
|
|
img_warnings = """
|
|
<i class="material-icons md-18 theme-muted2-text" title="No warnings">warning</i>
|
|
"""
|
|
self.html_template = (
|
|
"""
|
|
<div class="row">
|
|
<div class="col s6">
|
|
<span class="mt-0 mb-0 theme-primary-text font-weight-700" style="font-size: 36px">"""
|
|
+ img_tls
|
|
+ img_warnings
|
|
+ """</h3>
|
|
</div>
|
|
<div class="col s6 right-align">
|
|
<img height="48px" src="static/images/apps/docker.png" alt="Docker">
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<h6 class="font-weight-900 center theme-muted-text">{{name}}</h6>
|
|
</div>
|
|
<div class="row center-align">
|
|
<i class="material-icons-outlined">keyboard_arrow_down</i>
|
|
</div>
|
|
<div class="row center-align">
|
|
<div class="col s12">
|
|
<div class="collection theme-muted-text">
|
|
<div class="collection-item"><span class="font-weight-900">Containers: </span>{{ containers }}</div>
|
|
<div class="collection-item"><span class="font-weight-900">Running: </span>{{ containers_running }}</div>
|
|
<div class="collection-item"><span class="font-weight-900">Paused: </span>{{ containers_paused }}</div>
|
|
<div class="collection-item"><span class="font-weight-900">Stopped: </span>{{ containers_stopped }}</div>
|
|
<div class="collection-item"><span class="font-weight-900">Images: </span>{{ images }}</div>
|
|
<div class="collection-item"><span class="font-weight-900">Driver: </span>{{ driver }}</div>
|
|
<div class="collection-item"><span class="font-weight-900">CPU: </span>{{ cpu }}</div>
|
|
<div class="collection-item"><span class="font-weight-900">Memory: </span>{{ memory }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
)
|
|
|
|
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}}<p style="text-align:right;text-transform:uppercase;font-size:14px;font-family: monospace;">{{name}}<br /><i style="position: relative; top: .2rem" class="material-icons md-18 theme-success-text" title="Running">fiber_manual_record</i>{{containers_running}}<i style="position: relative; top: .2rem" class="material-icons md-18 theme-warning-text" title="Paused">fiber_manual_record</i>{{containers_paused}}<i style="position: relative; top: .2rem" class="material-icons md-18 theme-failure-text" title="Stopped">fiber_manual_record</i>{{containers_stopped}}</p>
|
|
|
|
[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}}<p style="text-align:right;text-transform:uppercase;font-size:14px;font-family: monospace;">{{name}}<br /><i style="position: relative; top: .2rem" class="material-icons md-18 theme-success-text" title="Running">fiber_manual_record</i>{{containers_running}}<i style="position: relative; top: .2rem" class="material-icons md-18 theme-warning-text" title="Paused">fiber_manual_record</i>{{containers_paused}}<i style="position: relative; top: .2rem" class="material-icons md-18 theme-failure-text" title="Stopped">fiber_manual_record</i>{{containers_stopped}}</p>
|
|
|
|
[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__)
|