Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
be50c2e763 | ||
|
ef23aec3f0 | ||
|
322612761d | ||
|
8358cc4749 | ||
|
519e123f77 | ||
|
f196180f06 | ||
|
c9027a69b2 | ||
|
a9864ad3e5 | ||
|
f93b77d89b |
58
README.md
58
README.md
@ -1,8 +1,26 @@
|
||||
# DashMachine
|
||||
### Another web application bookmark dashboard, with fun features.
|
||||

|
||||
|
||||
## Before Installing
|
||||
Please read the latest update post: https://www.reddit.com/r/DashMachine/comments/fqk8gl/version_05/
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
[](https://opensource.org/licenses/)
|
||||
[](https://github.com/sindresorhus/awesome)
|
||||
|
||||
[](https://liberapay.com/rmountjoy)
|
||||

|
||||
|
||||
Want a feature added now? [Open a bounty](https://www.bountysource.com/teams/dashmachine-app)
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||
@ -12,20 +30,6 @@ Please read the latest update post: https://www.reddit.com/r/DashMachine/comment
|
||||
|
||||

|
||||
|
||||
### Features
|
||||
* creates a dashboard to view web pages
|
||||
* uses a single .ini file for configuration
|
||||
* dark mode/light mode and accent colors
|
||||
* custom backgrounds and icons
|
||||
* web interface to edit the config file and add image files
|
||||
* ability to open web pages in current tab, new tab or iframe
|
||||
* hideable sidebar with dragable reveal button
|
||||
* user login system
|
||||
* 'app templates' which are sample config entries for popular self hosted apps
|
||||
* powerful plugin system for adding data from various sources to display on cards
|
||||
* multiple card types including collections and custom cards
|
||||
* multiple users, access groups, access settings
|
||||
* tagging system
|
||||
|
||||
## Installation
|
||||
### Docker
|
||||
@ -82,27 +86,13 @@ https://github.com/rmountjoy92/DashMachine/blob/master/pull_request_template.md
|
||||
See this link for how to create a pull request:
|
||||
https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
|
||||
|
||||
## Subreddit
|
||||
https://www.reddit.com/r/DashMachine
|
||||
|
||||
## Want to buy me a coffee?
|
||||
recurring:
|
||||
<a href="https://liberapay.com/rmountjoy/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||
|
||||
recurring or one-time:
|
||||
https://www.bountysource.com/teams/dashmachine-app
|
||||
|
||||
## Want a feature to be added faster?
|
||||
Open a bounty on https://www.bountysource.com/
|
||||
|
||||
Bountysource faq: https://github.com/bountysource/core/wiki/Frequently-Asked-Questions
|
||||
|
||||
## Tech used
|
||||
* Flask
|
||||
* SQLalchemy w/ SQLite
|
||||
* Jinja2
|
||||
* Flask (Python 3)
|
||||
* SQLalchemy w/ SQLite database
|
||||
* HTML5/Jinja2
|
||||
* Materialize css
|
||||
* JavaScript/jQuery/jQueryUI
|
||||
* .ini (for configuration)
|
||||
|
||||
## FAQs
|
||||
1. application does not work in iframe
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import uuid
|
||||
from flask import Flask
|
||||
from flask_caching import Cache
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
@ -11,13 +12,23 @@ from dashmachine.paths import user_data_folder
|
||||
if not os.path.isdir(user_data_folder):
|
||||
os.mkdir(user_data_folder)
|
||||
|
||||
secret_file = os.path.join(user_data_folder, ".secret")
|
||||
if not os.path.isfile(secret_file):
|
||||
with open(secret_file, "w") as new_file:
|
||||
new_file.write(uuid.uuid4().hex)
|
||||
|
||||
with open(secret_file, "r") as secret_file:
|
||||
secret_key = secret_file.read().encode("utf-8")
|
||||
if len(secret_key) < 32:
|
||||
secret_key = uuid.uuid4().hex
|
||||
|
||||
context_path = os.getenv("CONTEXT_PATH", "")
|
||||
app = Flask(__name__, static_url_path=context_path + "/static")
|
||||
cache = Cache(app, config={"CACHE_TYPE": "simple"})
|
||||
api = Api(app)
|
||||
|
||||
app.config["AVATARS_IDENTICON_BG"] = (255, 255, 255)
|
||||
app.config["SECRET_KEY"] = "66532a62c4048f976e22a39638b6f10e"
|
||||
app.config["SECRET_KEY"] = secret_key
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///user_data/site.db"
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 0
|
||||
|
394
dashmachine/platform/docker.py
Normal file
394
dashmachine/platform/docker.py
Normal file
@ -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 |
|
||||
<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__)
|
199
dashmachine/platform/healthchecks.py
Normal file
199
dashmachine/platform/healthchecks.py
Normal file
@ -0,0 +1,199 @@
|
||||
"""
|
||||
##### Healthchecks
|
||||
Display information from Healthchecks API
|
||||
```ini
|
||||
[variable_name]
|
||||
platform = healthchecks
|
||||
prefix = http://
|
||||
host = localhost
|
||||
port = 8080
|
||||
api_key = {{ Healthchecks project API Key }}
|
||||
project = {{ Healthchecks project name }}
|
||||
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. | healthchecks |
|
||||
| prefix | No | The prefix for the app's url. | web prefix, e.g. http:// or https:// |
|
||||
| host | Yes | Healthchecks Host | url,ip |
|
||||
| port | No | Healthchecks Port | port |
|
||||
| api_key | Yes | ApiKey | api key |
|
||||
| project | No | Healthchecks project name | project |
|
||||
| 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 |
|
||||
<br />
|
||||
###### **Available fields for value_template**
|
||||
* status
|
||||
* count_checks
|
||||
* count_up
|
||||
* count_down
|
||||
* count_grace
|
||||
* count_paused
|
||||
* error (for debug)
|
||||
> **Working example:**
|
||||
>```ini
|
||||
> [healthchecks-data]
|
||||
> platform = healthchecks
|
||||
> prefix = http://
|
||||
> host = 192.168.0.110
|
||||
> port = 8080
|
||||
> api_key = {{ API Key }}
|
||||
> project = {{ Project name }}
|
||||
> verify = False
|
||||
> value_template = {{error}}<p style="text-align:right;text-transform:uppercase;font-size:14px;font-family: monospace;"><i style="position: relative; top: .2rem" class="material-icons md-18 theme-success-text" title="Up">fiber_manual_record</i>{{count_up}}<i style="position: relative; top: .2rem" class="material-icons md-18 theme-warning-text" title="Grace">fiber_manual_record</i>{{count_grace}}<i style="position: relative; top: .2rem" class="material-icons md-18 theme-failure-text" title="Down">fiber_manual_record</i>{{count_down}}</p>
|
||||
>
|
||||
> [Healthchecks]
|
||||
> prefix = http://
|
||||
> url = 192.168.0.110
|
||||
> icon = static/images/apps/healthchecks.png
|
||||
> description = Healthchecks is a watchdog for your cron jobs. It's a web server that listens for pings from your cron jobs, plus a web interface.
|
||||
> open_in = this_tab
|
||||
> data_sources = healthchecks-data
|
||||
>```
|
||||
"""
|
||||
|
||||
import json
|
||||
from flask import render_template_string
|
||||
import requests
|
||||
|
||||
|
||||
class Healthchecks(object):
|
||||
def __init__(self, method, prefix, host, port, api_key, project, verify):
|
||||
self.endpoint = "/api/v1/checks/"
|
||||
self.method = method
|
||||
self.prefix = prefix
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.api_key = api_key
|
||||
self.project = project
|
||||
self.verify = verify
|
||||
|
||||
# Initialize results
|
||||
self.error = None
|
||||
self.status = ""
|
||||
self.count_checks = 0
|
||||
self.count_up = 0
|
||||
self.count_down = 0
|
||||
self.count_grace = 0
|
||||
self.count_paused = 0
|
||||
|
||||
def check(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint,
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
if "error" in rawdata:
|
||||
self.error = rawdata["error"]
|
||||
|
||||
def getChecks(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint,
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
for check in rawdata["checks"]:
|
||||
self.count_checks += 1
|
||||
if check["status"] == "up":
|
||||
self.count_up += 1
|
||||
if check["status"] == "down":
|
||||
self.count_down += 1
|
||||
if check["status"] == "grace":
|
||||
self.count_grace += 1
|
||||
if check["status"] == "paused":
|
||||
self.count_paused += 1
|
||||
|
||||
if self.count_down > 0:
|
||||
self.status = "down"
|
||||
if self.count_down == 0 and self.count_grace > 0:
|
||||
self.status = "grace"
|
||||
if self.count_down == 0 and self.count_grace == 0:
|
||||
self.status = "up"
|
||||
|
||||
def refresh(self):
|
||||
self.check()
|
||||
if self.error == None:
|
||||
self.error = ""
|
||||
self.getChecks()
|
||||
|
||||
|
||||
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 = None
|
||||
if not hasattr(self, "api_key"):
|
||||
self.api_key = None
|
||||
if not hasattr(self, "project"):
|
||||
self.project = None
|
||||
if not hasattr(self, "verify"):
|
||||
self.verify = True
|
||||
|
||||
self.healthchecks = Healthchecks(
|
||||
self.method,
|
||||
self.prefix,
|
||||
self.host,
|
||||
self.port,
|
||||
self.api_key,
|
||||
self.project,
|
||||
self.verify,
|
||||
)
|
||||
|
||||
def process(self):
|
||||
if self.api_key == None:
|
||||
return "api_key missing"
|
||||
if self.host == None:
|
||||
return "host missing"
|
||||
|
||||
self.healthchecks.refresh()
|
||||
value_template = render_template_string(
|
||||
self.value_template, **self.healthchecks.__dict__
|
||||
)
|
||||
return value_template
|
@ -65,7 +65,7 @@ class Platform:
|
||||
self.return_codes = "2xx,3xx"
|
||||
if not hasattr(self, "ssl_ignore"):
|
||||
self.ssl_ignore = "No"
|
||||
|
||||
|
||||
def process(self):
|
||||
# Check if method is within allowed methods for http_status
|
||||
if self.method.upper() not in ["GET", "HEAD", "OPTIONS", "TRACE"]:
|
||||
@ -87,7 +87,7 @@ class Platform:
|
||||
)
|
||||
prepped = req.prepare()
|
||||
if self.ssl_ignore == "yes":
|
||||
resp = s.send(prepped,verify=False)
|
||||
resp = s.send(prepped, verify=False)
|
||||
else:
|
||||
resp = s.send(prepped)
|
||||
resp = s.send(prepped)
|
||||
|
290
dashmachine/platform/lidarr.py
Normal file
290
dashmachine/platform/lidarr.py
Normal file
@ -0,0 +1,290 @@
|
||||
"""
|
||||
##### 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 |
|
||||
<br />
|
||||
###### **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}}<br />Queue : {{queue}} <br />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
|
||||
|
||||
|
||||
class Lidarr(object):
|
||||
def __init__(self, method, prefix, host, port, api_key, api_version, verify):
|
||||
self.api_version = api_version
|
||||
self.endpoint = "/api/" + self.api_version
|
||||
self.method = method
|
||||
self.prefix = prefix
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.api_key = api_key
|
||||
self.verify = verify
|
||||
|
||||
# Initialize results
|
||||
self.error = None
|
||||
self.version = "?"
|
||||
self.wanted_missing = 0
|
||||
self.wanted_cutoff = 0
|
||||
self.queue = 0
|
||||
self.diskspace = [
|
||||
{"path": "", "total": "", "free": "", "used": ""},
|
||||
{"path": "", "total": "", "free": "", "used": ""},
|
||||
]
|
||||
|
||||
def check(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/system/status",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
if "error" in rawdata:
|
||||
self.error = rawdata["error"]
|
||||
|
||||
def getVersion(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/system/status",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.version = rawdata["version"]
|
||||
|
||||
def getWanted(self, wType):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix
|
||||
+ self.host
|
||||
+ port
|
||||
+ self.endpoint
|
||||
+ "/wanted/"
|
||||
+ wType
|
||||
+ "/",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
if wType == "missing":
|
||||
self.wanted_missing = rawdata["totalRecords"]
|
||||
else:
|
||||
self.wanted_cutoff = rawdata["totalRecords"]
|
||||
|
||||
def getQueue(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/queue",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.queue = rawdata["totalRecords"]
|
||||
|
||||
def getDiskspace(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/diskspace",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.diskspace = rawdata
|
||||
for item in self.diskspace:
|
||||
item["used"] = self.formatSize(item["totalSpace"] - item["freeSpace"])
|
||||
item["total"] = self.formatSize(item["totalSpace"])
|
||||
item["free"] = self.formatSize(item["freeSpace"])
|
||||
item.pop("totalSpace", None)
|
||||
item.pop("freeSpace", None)
|
||||
|
||||
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.getVersion()
|
||||
self.getWanted("missing")
|
||||
self.getWanted("cutoff")
|
||||
self.getQueue()
|
||||
self.getDiskspace()
|
||||
|
||||
|
||||
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 = None
|
||||
if not hasattr(self, "api_key"):
|
||||
self.api_key = None
|
||||
if not hasattr(self, "api_version"):
|
||||
self.api_version = "v1"
|
||||
if not hasattr(self, "verify"):
|
||||
self.verify = True
|
||||
|
||||
self.lidarr = Lidarr(
|
||||
self.method,
|
||||
self.prefix,
|
||||
self.host,
|
||||
self.port,
|
||||
self.api_key,
|
||||
self.api_version,
|
||||
self.verify,
|
||||
)
|
||||
|
||||
def process(self):
|
||||
if self.api_key == None:
|
||||
return "api_key missing"
|
||||
if self.host == None:
|
||||
return "host missing"
|
||||
|
||||
self.lidarr.refresh()
|
||||
value_template = render_template_string(
|
||||
self.value_template, **self.lidarr.__dict__
|
||||
)
|
||||
return value_template
|
95
dashmachine/platform/plex.py
Normal file
95
dashmachine/platform/plex.py
Normal file
@ -0,0 +1,95 @@
|
||||
"""
|
||||
##### Plex Media Server
|
||||
Connect to Plex Media Server and see current sessions details
|
||||
```ini
|
||||
[variable_name]
|
||||
platform = plex
|
||||
url = http://plex_host:plex_port
|
||||
token = plex_token
|
||||
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. | plex |
|
||||
| host | Yes | URL of Plex Media Server (include port, normally 32400) | url |
|
||||
| token | Yes | X-Plex-Token (See [here](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) for how to find it.) | string |
|
||||
| value_template | Yes | Jinja template for how the returned data from API is displayed. | jinja template |
|
||||
<br />
|
||||
###### **Available fields for value_template**
|
||||
* sessions
|
||||
* transcodes
|
||||
* libraries
|
||||
> **Example:**
|
||||
>```ini
|
||||
>[plex]
|
||||
>platform = plex
|
||||
>host = http://plex.example.com:32400
|
||||
>token = abcde_fghi_jklmnopqr
|
||||
>value_template = Sessions: {{sessions}}<br />Transcodes: {{transcodes}}
|
||||
>
|
||||
>[Plex]
|
||||
>prefix = http://
|
||||
>url = plex.example.com:32400
|
||||
>icon = static/images/apps/plex.png
|
||||
>description = Plex data sources example
|
||||
>open_in = this_tab
|
||||
>data_sources = plex
|
||||
>```
|
||||
"""
|
||||
import requests
|
||||
from flask import render_template_string
|
||||
|
||||
json_header = {"Accept": "application/json"}
|
||||
|
||||
|
||||
class Plex(object):
|
||||
def __init__(self, url, token):
|
||||
self.url = url
|
||||
self.token = token
|
||||
|
||||
def refresh(self):
|
||||
if self.token != None:
|
||||
sessions = requests.get(
|
||||
self.url + "/status/sessions?X-Plex-Token=" + self.token,
|
||||
headers=json_header,
|
||||
).json()
|
||||
|
||||
self.sessions = sessions["MediaContainer"]["size"]
|
||||
|
||||
transcodes = requests.get(
|
||||
self.url + "/transcode/sessions?X-Plex-Token=" + self.token,
|
||||
headers=json_header,
|
||||
).json()
|
||||
|
||||
self.transcodes = transcodes["MediaContainer"]["size"]
|
||||
|
||||
libraries = requests.get(
|
||||
self.url + "/library/sections?X-Plex-Token=" + self.token,
|
||||
headers=json_header,
|
||||
).json()
|
||||
|
||||
self.libraries = libraries["MediaContainer"]["size"]
|
||||
|
||||
|
||||
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
|
||||
|
||||
if not hasattr(self, "token"):
|
||||
print(
|
||||
"Please add a token\nSee https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/ to find it."
|
||||
)
|
||||
exit(1)
|
||||
else:
|
||||
self.plex = Plex(self.host, self.token)
|
||||
|
||||
def process(self):
|
||||
self.plex.refresh()
|
||||
value_template = render_template_string(
|
||||
self.value_template, **self.plex.__dict__
|
||||
)
|
||||
return value_template
|
268
dashmachine/platform/radarr.py
Normal file
268
dashmachine/platform/radarr.py
Normal file
@ -0,0 +1,268 @@
|
||||
"""
|
||||
##### Radarr
|
||||
Display information from Radarr API
|
||||
```ini
|
||||
[variable_name]
|
||||
platform = radarr
|
||||
prefix = http://
|
||||
host = localhost
|
||||
port = 7878
|
||||
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. | radarr |
|
||||
| prefix | No | The prefix for the app's url. | web prefix, e.g. http:// or https:// |
|
||||
| host | Yes | Radarr Host | url,ip |
|
||||
| port | No | Radarr Port | port |
|
||||
| api_key | Yes | ApiKey | api key |
|
||||
| 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 |
|
||||
<br />
|
||||
###### **Available fields for value_template**
|
||||
* version
|
||||
* movies
|
||||
* queue
|
||||
* diskspace[x]['path']
|
||||
* diskspace[x]['total']
|
||||
* diskspace[x]['used']
|
||||
* diskspace[x]['free']
|
||||
* error (for debug)
|
||||
> **Working example:**
|
||||
>```ini
|
||||
> [radarr-data]
|
||||
> platform = radarr
|
||||
> prefix = http://
|
||||
> host = 192.168.0.110
|
||||
> port = 7878
|
||||
> api_key = {{ API Key }}
|
||||
> verify = False
|
||||
> value_template = {{error}}Movies : {{movies}}<br />Queue : {{queue}} <br />Free ({{diskspace[0]['path']}}) : {{diskspace[0]['free']}}
|
||||
>
|
||||
> [Radarr]
|
||||
> prefix = http://
|
||||
> url = 192.168.0.110:7878
|
||||
> icon = static/images/apps/radarr.png
|
||||
> sidebar_icon = static/images/apps/radarr.png
|
||||
> description = A fork of Sonarr to work with movies à la Couchpotato
|
||||
> open_in = this_tab
|
||||
> data_sources = radarr-data
|
||||
>```
|
||||
"""
|
||||
|
||||
import json
|
||||
from flask import render_template_string
|
||||
import requests
|
||||
|
||||
|
||||
class Radarr(object):
|
||||
def __init__(self, method, prefix, host, port, api_key, verify):
|
||||
self.endpoint = "/api"
|
||||
self.method = method
|
||||
self.prefix = prefix
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.api_key = api_key
|
||||
self.verify = verify
|
||||
|
||||
# Initialize results
|
||||
self.error = None
|
||||
self.version = "?"
|
||||
self.movies = 0
|
||||
self.queue = 0
|
||||
self.diskspace = [
|
||||
{"path": "", "total": "", "free": "", "used": ""},
|
||||
{"path": "", "total": "", "free": "", "used": ""},
|
||||
]
|
||||
|
||||
def check(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/system/status",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
if "error" in rawdata:
|
||||
self.error = rawdata["error"]
|
||||
|
||||
def getVersion(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/system/status",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.version = rawdata["version"]
|
||||
|
||||
def getMovies(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/movie",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.movies = len(rawdata)
|
||||
|
||||
def getQueue(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/queue",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.queue = len((rawdata))
|
||||
|
||||
def getDiskspace(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/diskspace",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.diskspace = rawdata
|
||||
for item in self.diskspace:
|
||||
item["used"] = self.formatSize(item["totalSpace"] - item["freeSpace"])
|
||||
item["total"] = self.formatSize(item["totalSpace"])
|
||||
item["free"] = self.formatSize(item["freeSpace"])
|
||||
item.pop("totalSpace", None)
|
||||
item.pop("freeSpace", None)
|
||||
|
||||
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.getVersion()
|
||||
self.getMovies()
|
||||
self.getQueue()
|
||||
self.getDiskspace()
|
||||
|
||||
|
||||
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 = None
|
||||
if not hasattr(self, "api_key"):
|
||||
self.api_key = None
|
||||
if not hasattr(self, "verify"):
|
||||
self.verify = True
|
||||
|
||||
self.radarr = Radarr(
|
||||
self.method, self.prefix, self.host, self.port, self.api_key, self.verify
|
||||
)
|
||||
|
||||
def process(self):
|
||||
if self.api_key == None:
|
||||
return "api_key missing"
|
||||
if self.host == None:
|
||||
return "host missing"
|
||||
|
||||
self.radarr.refresh()
|
||||
value_template = render_template_string(
|
||||
self.value_template, **self.radarr.__dict__
|
||||
)
|
||||
return value_template
|
268
dashmachine/platform/sonarr.py
Normal file
268
dashmachine/platform/sonarr.py
Normal file
@ -0,0 +1,268 @@
|
||||
"""
|
||||
##### Sonarr
|
||||
Display information from Sonarr API
|
||||
```ini
|
||||
[variable_name]
|
||||
platform = sonarr
|
||||
prefix = http://
|
||||
host = localhost
|
||||
port = 8989
|
||||
api_key = {{ Sonarr 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. | sonarr |
|
||||
| prefix | No | The prefix for the app's url. | web prefix, e.g. http:// or https:// |
|
||||
| host | Yes | Sonarr Host | url,ip |
|
||||
| port | No | Sonarr Port | port |
|
||||
| api_key | Yes | ApiKey | api key |
|
||||
| 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 |
|
||||
<br />
|
||||
###### **Available fields for value_template**
|
||||
* version
|
||||
* wanted_missing
|
||||
* queue
|
||||
* diskspace[x]['path']
|
||||
* diskspace[x]['total']
|
||||
* diskspace[x]['used']
|
||||
* diskspace[x]['free']
|
||||
* error (for debug)
|
||||
> **Working example:**
|
||||
>```ini
|
||||
> [sonarr-data]
|
||||
> platform = sonarr
|
||||
> prefix = http://
|
||||
> host = 192.168.0.110
|
||||
> port = 8989
|
||||
> api_key = {{ API Key }}
|
||||
> verify = False
|
||||
> value_template = {{error}}Missing : {{wanted_missing}}<br />Queue : {{queue}} <br />Free ({{diskspace[0]['path']}}) : {{diskspace[0]['free']}}
|
||||
>
|
||||
> [Sonarr]
|
||||
> prefix = http://
|
||||
> url = 192.168.0.110:8989
|
||||
> icon = static/images/apps/sonarr.png
|
||||
> sidebar_icon = static/images/apps/sonarr.png
|
||||
> description = Smart PVR for newsgroup and bittorrent users
|
||||
> open_in = this_tab
|
||||
> data_sources = sonarr-data
|
||||
>```
|
||||
"""
|
||||
|
||||
import json
|
||||
from flask import render_template_string
|
||||
import requests
|
||||
|
||||
|
||||
class Sonarr(object):
|
||||
def __init__(self, method, prefix, host, port, api_key, verify):
|
||||
self.endpoint = "/api"
|
||||
self.method = method
|
||||
self.prefix = prefix
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.api_key = api_key
|
||||
self.verify = verify
|
||||
|
||||
# Initialize results
|
||||
self.error = None
|
||||
self.version = "?"
|
||||
self.wanted_missing = 0
|
||||
self.queue = 0
|
||||
self.diskspace = [
|
||||
{"path": "", "total": "", "free": "", "used": ""},
|
||||
{"path": "", "total": "", "free": "", "used": ""},
|
||||
]
|
||||
|
||||
def check(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/system/status",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
if "error" in rawdata:
|
||||
self.error = rawdata["error"]
|
||||
|
||||
def getVersion(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/system/status",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.version = rawdata["version"]
|
||||
|
||||
def getWanted(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/wanted/missing/",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.wanted_missing = rawdata["totalRecords"]
|
||||
|
||||
def getQueue(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/queue",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.queue = len(rawdata)
|
||||
|
||||
def getDiskspace(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix + self.host + port + self.endpoint + "/diskspace",
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.diskspace = rawdata
|
||||
for item in self.diskspace:
|
||||
item["used"] = self.formatSize(item["totalSpace"] - item["freeSpace"])
|
||||
item["total"] = self.formatSize(item["totalSpace"])
|
||||
item["free"] = self.formatSize(item["freeSpace"])
|
||||
item.pop("totalSpace", None)
|
||||
item.pop("freeSpace", None)
|
||||
|
||||
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.getVersion()
|
||||
self.getWanted()
|
||||
self.getQueue()
|
||||
self.getDiskspace()
|
||||
|
||||
|
||||
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 = None
|
||||
if not hasattr(self, "api_key"):
|
||||
self.api_key = None
|
||||
if not hasattr(self, "verify"):
|
||||
self.verify = True
|
||||
|
||||
self.sonarr = Sonarr(
|
||||
self.method, self.prefix, self.host, self.port, self.api_key, self.verify
|
||||
)
|
||||
|
||||
def process(self):
|
||||
if self.api_key == None:
|
||||
return "api_key missing"
|
||||
if self.host == None:
|
||||
return "host missing"
|
||||
|
||||
self.sonarr.refresh()
|
||||
value_template = render_template_string(
|
||||
self.value_template, **self.sonarr.__dict__
|
||||
)
|
||||
return value_template
|
224
dashmachine/platform/tautulli.py
Normal file
224
dashmachine/platform/tautulli.py
Normal file
@ -0,0 +1,224 @@
|
||||
"""
|
||||
##### Tautulli
|
||||
Display information from Tautulli API
|
||||
```ini
|
||||
[variable_name]
|
||||
platform = tautulli
|
||||
prefix = http://
|
||||
host = localhost
|
||||
port = 8181
|
||||
api_key = {{ Tautulli 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. | tautulli |
|
||||
| prefix | No | The prefix for the app's url. | web prefix, e.g. http:// or https:// |
|
||||
| host | Yes | Tautulli Host | url,ip |
|
||||
| port | No | Tautulli Port | port |
|
||||
| api_key | Yes | ApiKey | api key |
|
||||
| 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 |
|
||||
<br />
|
||||
###### **Available fields for value_template**
|
||||
* stream_count
|
||||
* stream_count_direct_play
|
||||
* stream_count_direct_stream
|
||||
* stream_count_transcode
|
||||
* total_bandwidth
|
||||
* wan_bandwidth
|
||||
* update_available
|
||||
* update_message
|
||||
* error (for debug)
|
||||
> **Working example:**
|
||||
>```ini
|
||||
> [tautulli-data]
|
||||
> platform = tautulli
|
||||
> prefix = http://
|
||||
> host = 192.168.0.110
|
||||
> port = 8181
|
||||
> api_key = myApiKey
|
||||
> verify = False
|
||||
> value_template = {{error}}Active sessions : {{stream_count}}
|
||||
>
|
||||
> [Tautulli]
|
||||
> prefix = http://
|
||||
> url = 192.168.0.110:8181
|
||||
> icon = static/images/apps/tautulli.png
|
||||
> sidebar_icon = static/images/apps/tautulli.png
|
||||
> description = A Python based monitoring and tracking tool for Plex Media Server
|
||||
> open_in = this_tab
|
||||
> data_sources = tautulli-data
|
||||
>```
|
||||
"""
|
||||
|
||||
from flask import render_template_string
|
||||
import requests
|
||||
|
||||
|
||||
class Tautulli(object):
|
||||
def __init__(self, method, prefix, host, port, api_key, verify):
|
||||
self.endpoint = "/api/v2"
|
||||
self.method = method
|
||||
self.prefix = prefix
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.api_key = api_key
|
||||
self.verify = verify
|
||||
|
||||
# Initialize results
|
||||
self.error = None
|
||||
self.update_available = ""
|
||||
self.update_message = ""
|
||||
self.stream_count = ""
|
||||
|
||||
def check(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix
|
||||
+ self.host
|
||||
+ port
|
||||
+ self.endpoint
|
||||
+ "?apikey="
|
||||
+ self.api_key
|
||||
+ "&cmd="
|
||||
+ "update_check",
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
if "response" in rawdata and rawdata["response"]["result"] == "error":
|
||||
self.error = rawdata["response"]["message"]
|
||||
|
||||
def getUpdate(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix
|
||||
+ self.host
|
||||
+ port
|
||||
+ self.endpoint
|
||||
+ "?apikey="
|
||||
+ self.api_key
|
||||
+ "&cmd="
|
||||
+ "update_check",
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.update_message = rawdata["response"]["message"]
|
||||
self.update_available = rawdata["response"]["data"]["update"]
|
||||
|
||||
def getActivity(self):
|
||||
verify = (
|
||||
False
|
||||
if str(self.verify).lower() == "false"
|
||||
or str(self.prefix).lower() == "http://"
|
||||
else True
|
||||
)
|
||||
port = "" if self.port == None else ":" + self.port
|
||||
|
||||
if self.method.upper() == "GET":
|
||||
try:
|
||||
rawdata = requests.get(
|
||||
self.prefix
|
||||
+ self.host
|
||||
+ port
|
||||
+ self.endpoint
|
||||
+ "?apikey="
|
||||
+ self.api_key
|
||||
+ "&cmd="
|
||||
+ "get_activity",
|
||||
verify=verify,
|
||||
timeout=10,
|
||||
).json()
|
||||
except Exception as e:
|
||||
rawdata = None
|
||||
self.error = f"{e}"
|
||||
|
||||
if rawdata != None:
|
||||
self.stream_count = rawdata["response"]["data"]["stream_count"]
|
||||
self.stream_count_direct_play = rawdata["response"]["data"][
|
||||
"stream_count_direct_play"
|
||||
]
|
||||
self.stream_count_direct_stream = rawdata["response"]["data"][
|
||||
"stream_count_direct_stream"
|
||||
]
|
||||
self.stream_count_transcode = rawdata["response"]["data"][
|
||||
"stream_count_transcode"
|
||||
]
|
||||
self.total_bandwidth = rawdata["response"]["data"]["total_bandwidth"]
|
||||
self.wan_bandwidth = rawdata["response"]["data"]["wan_bandwidth"]
|
||||
|
||||
def refresh(self):
|
||||
self.check()
|
||||
if self.error == None:
|
||||
self.error = ""
|
||||
self.getUpdate()
|
||||
self.getActivity()
|
||||
|
||||
|
||||
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 = None
|
||||
if not hasattr(self, "api_key"):
|
||||
self.api_key = None
|
||||
if not hasattr(self, "verify"):
|
||||
self.verify = True
|
||||
|
||||
self.tautulli = Tautulli(
|
||||
self.method, self.prefix, self.host, self.port, self.api_key, self.verify
|
||||
)
|
||||
|
||||
def process(self):
|
||||
if self.api_key == None:
|
||||
return "api_key missing"
|
||||
if self.host == None:
|
||||
return "host missing"
|
||||
|
||||
self.tautulli.refresh()
|
||||
value_template = render_template_string(
|
||||
self.value_template, **self.tautulli.__dict__
|
||||
)
|
||||
return value_template
|
BIN
dashmachine/static/images/apps/docker.png
Normal file
BIN
dashmachine/static/images/apps/docker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
@ -1,2 +1,2 @@
|
||||
version = "v0.5"
|
||||
revision_number = "4"
|
||||
revision_number = "4.1"
|
||||
|
7
template_apps/Docker.ini
Normal file
7
template_apps/Docker.ini
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user