Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
382 changes: 382 additions & 0 deletions dashmachine/platform/docker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,382 @@
"""

##### 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 |

<br />
###### **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}}<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
>```
"""

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 = """
<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 __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__
)
Binary file added dashmachine/static/images/apps/docker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions template_apps/Docker.ini
Original file line number Diff line number Diff line change
@@ -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