commit 6e9b3aed3a3a55597087e4b76d2501b9a8302374 Author: Krzysztof Płaczek Date: Fri Mar 9 10:33:19 2018 +0100 Initial commit of docker flask. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf6d7e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +/__pycache__ diff --git a/images.py b/images.py new file mode 100644 index 0000000..ec2749f --- /dev/null +++ b/images.py @@ -0,0 +1,10 @@ + + +@app.route("/") +def index(): + data = [] + for client, value in clients.items(): + data.append({'info': value.info(), 'version': value.version(), 'client_name': client, + 'volumes': len(value.volumes.list())}) + return render_template('index.html', data=data) + diff --git a/index.py b/index.py new file mode 100644 index 0000000..01b7ced --- /dev/null +++ b/index.py @@ -0,0 +1,384 @@ +# -*- coding: utf-8 -*- +from flask import Flask, request, flash, render_template, redirect, Response, url_for +import docker +import re +import unidecode +import datetime +import base64 + +# +# from flask_wtf import FlaskForm +# from wtforms import StringField +# from wtforms.validators import DataRequired + +clients = {'Docker local': docker.from_env(), 'Docker 132': docker.DockerClient(base_url="tcp://10.66.4.132:2375")} + +client = docker.from_env() +app = Flask(__name__) + +# +# class RenameContainer(FlaskForm): +# name = StringField('name', validators=[DataRequired()]) +# + + +SECRET_KEY = b'secret' + + +@app.route("/") +def index(): + data = [] + for client, value in clients.items(): + try: + data.append({'info': value.info(), 'version': value.version(), 'client_name': client, + 'volumes': len(value.volumes.list()), 'networks': len(value.networks.list()), + 'df': value.df()}) + except: + pass + + return render_template('index.html', data=data) + + +# +# IMAGES +# + +@app.route('//images/dangling') +def images_dangling_action(client_name): + if client_name in clients: + images = clients[client_name].images.list(filters={'dangling': True}) + return render_template('images.html', images=images, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//images/all') +def images_all_action(client_name): + if client_name in clients: + images = clients[client_name].images.list(all=True) + return render_template('images.html', images=images, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//images/id/') +def image_action(client_name, image_id): + if client_name in clients: + image = clients[client_name].images.get(image_id) + return render_template('image.html', image=image, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//images') +def images_action(client_name): + if client_name in clients: + images = clients[client_name].images.list() + return render_template('images.html', images=images, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//images/export/') +def export_image_action(client_name, image_tag=None): + if client_name in clients: + image = clients[client_name].images.get(decode64(image_tag)) + return Response(image.save().stream(), mimetype='application/tar', + headers={"Content-disposition": "attachment; filename=%s_%s_%s.tar" % + ( + slugify(client_name), + datetime.date.today().strftime("%Y-%m-%d"), + slugify(image.attrs['RepoTags'][0]) + )}) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//images/remove/') +def remove_image_action(client_name, image_tag=None): + if client_name in clients: + images = clients[client_name].images + image = images.get(decode64(image_tag)) + flash('Image \'%s\' removed' % image.attrs['RepoTags'][0]) + images.remove(decode64(image_tag)) + return redirect(request.referrer) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//images/remove_by_id/') +def remove_imageby_id_action(client_name, image_id=None): + if client_name in clients: + flash('Image \'%s\' removed' % image_id) + clients[client_name].images.remove(image_id) + return redirect(request.referrer) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +# +# CONTAINERS +# + + +@app.route('//rename_container', methods=['POST']) +def rename_container(client_name): + if client_name in clients: + if request.form['name'] and request.form['short_id']: + container = clients[client_name].containers.get(request.form['short_id']) + container.rename(request.form['name'].strip()) + flash('Renamed container') + return redirect(url_for('containers_action')) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/') +def containers_compose_action(client_name, compose_project): + if client_name in clients: + containers = clients[client_name].containers.list( + filters={'label': "com.docker.compose.project=" + compose_project}) + containers.sort(key=lambda x: x.name) + containers.sort(key=lambda x: x.attrs['Config']['Labels'].get('com.docker.compose.project', '')) + return render_template('containers.html', containers=containers, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers') +def containers_action(client_name): + if client_name in clients: + containers = clients[client_name].containers.list() + containers.sort(key=lambda x: x.name) + containers.sort(key=lambda x: x.attrs['Config']['Labels'].get('com.docker.compose.project', '')) + + return render_template('containers.html', containers=containers, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/all') +def containers_all_action(client_name): + if client_name in clients: + containers = clients[client_name].containers.list(all=True) + # sortowanie po nazwie a potem po docker compose project name + containers.sort(key=lambda x: x.name) + containers.sort(key=lambda x: x.attrs['Config']['Labels'].get('com.docker.compose.project', '')) + return render_template('containers.html', containers=containers, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/id/') +def container_action(client_name, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + return render_template('container.html', container=container, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/log/', defaults={"timestamp": 0}) +@app.route('//containers/log//') +def log_action(client_name, timestamp, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + logs = container.logs(timestamps=timestamp > 0).replace(b'\n', bytes("
", 'utf-8')) + return render_template('log.html', container=container, logs=logs.decode(), client_name=client_name, + short_id=short_id) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/top/') +def top_action(client_name, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + top = container.top() + return render_template('top.html', container=container, top=top, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/export/') +def export_container_action(client_name, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + return Response(container.export().stream(), mimetype='application/tar', + headers={"Content-disposition": "attachment; filename=%s_%s_%s_%s.tar" % + ( + slugify(client_name), + datetime.date.today().strftime("%Y-%m-%d"), + container.short_id, + slugify(container.name) + )}) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/remove/') +def remove_container_action(client_name, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + container.remove(v=True) + flash('Removed container \'%s\' ' % container.name) + return redirect(request.referrer) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/stop/') +def stop_container_action(client_name, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + container.stop() + flash('Stopped container \'%s\' ' % container.name) + return redirect(request.referrer) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/start/') +def start_container_action(client_name, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + container.start() + flash('Started container \'%s\' ' % container.name) + return redirect(request.referrer) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/restart/') +def restart_container_action(client_name, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + container.restart() + flash('Restarted container \'%s\' ' % container.name) + return redirect(request.referrer) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//containers/') +def containers_status_action(client_name, status=None): + if client_name in clients: + containers = clients[client_name].containers.list(filters={'status': status}) + return render_template('containers.html', containers=containers, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +# +# VOLUMES +# + + +@app.route('//volumes') +def volumes_action(client_name): + if client_name in clients: + volumes = clients[client_name].volumes.list() + return render_template('volumes.html', volumes=volumes, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//volumes/prune') +def volumes_prune_action(client_name): + if client_name in clients: + prune_data = clients[client_name].volumes.prune() + + if prune_data['VolumesDeleted'] is not None: + prune_text = 'Deleted %d volumes: and reclaimed %s space' % ( + len(prune_data['VolumesDeleted']), file_size(prune_data['SpaceReclaimed']) + ) + else: + prune_text = "Deleted 0 volumes and reclaimed 0bytes." + + flash(prune_text) + return redirect(url_for('volumes_action', client_name=client_name)) + + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +@app.route('//volumes/id/') +def volume_action(client_name, short_id=None): + if client_name in clients: + container = clients[client_name].containers.get(short_id) + return render_template('container.html', container=container, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +# +# NETWORKS +# + +@app.route('//networks') +def networks_action(client_name): + if client_name in clients: + volumes = clients[client_name].networks.list() + return render_template('networks.html', networks=volumes, client_name=client_name) + else: + flash('Client name \'%s\' not found' % client_name) + return redirect(url_for('index')) + + +# +# TEMPLATE +# + + +@app.template_filter('file_size') +def file_size(num): + suffix = 'B' + for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: + if abs(num) < 1024.0: + return "%3.1f %s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s %s" % (num, 'Yi', suffix) + + +@app.template_filter('encode64') +def encode64(data): + return base64.b64encode(str.encode(data)) + + +@app.template_filter('decode64') +def decode64(data): + return base64.b64decode(data).decode() + + +def slugify(text): + text = unidecode.unidecode(text).lower() + return re.sub(r'\W+', '_', text) + + +app.secret_key = b'\xd7:o\\\xaayFe\x1ey\x08m9\xe4\xbc!\xee\x0e>\xd1Z\x99-\xbb' + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5002, debug=True, threaded=True) diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..ce2e6a6 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..58bc3e3 --- /dev/null +++ b/static/style.css @@ -0,0 +1,113 @@ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + + +body { + color: #444; + font-family: 'Ubuntu', sans-serif; + font-size: 62.5%; +} + +.container { max-width: 90%; margin: 0 auto; font-size: 1.4em;} +table {} +table tbody tr:hover { background-color: #f3f3f3; } +table th {color: #222} +table th, table td { +padding: 7px 22px; +border: 1px solid #ccc; +min-width: 140px; +} +table tbody tr:nth-child(2n+1) { +background-color: #f9f9f9; } + +h1 { font-size: 2.75em; margin: 16px 32px 16px 32px;} +h2 { font-size: 2em; margin: 16px 32px 16px 16px;} +h3 { font-size: 1.5em; margin: 16px 32px 16px 32px;} + +a {color: dodgerblue; text-decoration: none; } +a:hover { text-decoration: underline; } + +.button--link { margin: 10px 10px; display: inline-block;} + +.logs {overflow: scroll; +font-family: monospace; +max-height: 90vh; +max-height: 85vh; +white-space: nowrap; +background: #000; +color: #c0c0c0; +padding: 15px; +font-size: 1.1em; +} + +.flashes { +border: solid #e0e0e0; +border-width: 0 1px 1px 1px; +border-radius: 0 0 5px 5px; +padding: 15px; +} +.flashes li { +padding: 5px 15px; +} + + +.search_table { display: block; margin: 10px; padding: 5px 15px; } + + +.details {border-bottom: 1px solid #e9e9e9; margin-bottom: 10px; margin-left: 30px;} +.details + h2 { margin-top: 45px; } +.details > div {} +.details__id {display: inline-block; width: 200px;} +.details__name {display: inline-block; width: 350px; } +.details__status {display: inline-block; padding: 5px 10px; width: 100px; width: 60px; margin-right: 20px; text-align: center;} +.details__status--running {background-color: #9aed9a } +.details__status--exited {background-color: #d5d5d5} +.details__status--created {background-color: #ffc253; } +.details__controll { } +.details__created { display: inline-block; width: 220px; margin: 5px 10px;} +.details__entrypoint { display: inline-block; width: 210px; } +.details__image { display: inline-block; } + +.details__more-info {display: none; } \ No newline at end of file diff --git a/templates/container.html b/templates/container.html new file mode 100644 index 0000000..69af4fa --- /dev/null +++ b/templates/container.html @@ -0,0 +1,17 @@ +{% extends "layout.html" %} +{% block body %} + +

Dashboard » {{ client_name }} » Containers » {{ container.name }}

+ +
+

+

+ +

Details

+
+{{ container.attrs|pprint }}
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/containers.html b/templates/containers.html new file mode 100644 index 0000000..c23c7ff --- /dev/null +++ b/templates/containers.html @@ -0,0 +1,108 @@ +{% extends "layout.html" %} +{% block body %} + + +

Dashboard » {{ client_name }} » Containers

+ + +All Containers +Paused Containers +Running Containers +Stopped Containers + + + + + +

{{ currentComposerProject }}

+ + +{% for container in containers %} + + +{% if (loop.first) or (loop.previtem.attrs.Config.Labels['com.docker.compose.project'] != container.attrs.Config.Labels['com.docker.compose.project']) %} + + +

+ {{ + container.attrs.Config.Labels['com.docker.compose.project'] }}

+{% endif%} + + + + + + + + + + + + + + + + + +
+
+
{{ container.status }}
+ +
ShortID: {{ container.short_id }}
+
+
+
{{ container.attrs['Created'][0:19] }}
+
{{ container.attrs['Config']['Entrypoint'][0] }}
+
{{ container.attrs['Config']['Image'] }}
+
+
+
+ {% for port in container.attrs['NetworkSettings']['Ports'] %} + {{ port }} + {% if container.attrs['NetworkSettings']['Ports'][port] is not none %} + + {% if container.attrs['NetworkSettings']['Ports'][port] is not none and + container.attrs['NetworkSettings']['Ports'][port]|length > 0 %} + {% for port_exposed in container.attrs['NetworkSettings']['Ports'][port] %} + ->{{ port_exposed['HostIp'] }}:{{ port_exposed['HostPort'] }} + + {% endfor %} + {% endif %} + {% endif %} + {% endfor %} +
+
+
+ + +
+
+ log + export + {% if (container.status == 'exited') or (container.status == 'created') %} + start + remove + {% endif %} + + {% if container.status == 'running' %} + stop + restart + top + {% endif %} +
+
+ +{% else %} +
+
+ no containers +
+
+ +{% endfor %} + + +{% endblock %} \ No newline at end of file diff --git a/templates/containers.table.html b/templates/containers.table.html new file mode 100644 index 0000000..c870adc --- /dev/null +++ b/templates/containers.table.html @@ -0,0 +1,103 @@ +{% extends "layout.html" %} +{% block body %} + + +

Dashboard » {{ client_name }} » Containers

+ + +All Containers +Paused Containers +Running Containers +Stopped Containers + + + + + +

{{ currentComposerProject }}

+ + + + {% for container in containers %} + + + {% if (loop.first) or (loop.previtem.attrs.Config.Labels['com.docker.compose.project'] != container.attrs.Config.Labels['com.docker.compose.project']) %} + + + {% if (loop.previtem is defined) and (loop.previtem.attrs.Config.Labels['com.docker.compose.project'] != container.attrs.Config.Labels['com.docker.compose.project'])%} + + + + + {% endif %} + + +

{{ container.attrs.Config.Labels['com.docker.compose.project'] }}

+ + + + + + + + + + + + + + + + {% endif%} + + + + + + + + + + + + + + + {% else %} + + + + + {% endfor %} + + + +{% endblock %} \ No newline at end of file diff --git a/templates/image.html b/templates/image.html new file mode 100644 index 0000000..309dc29 --- /dev/null +++ b/templates/image.html @@ -0,0 +1,12 @@ +{% extends "layout.html" %} +{% block body %} + +

Dashboard » {{ client_name }} » Images + » {% if image.attrs['RepoTags']|length %} {{ image.attrs['RepoTags'][0] }} {% else %} {{ image.attrs['Id'][7:] + }} {% endif %}

+ +

Details

+
+    {{ image.attrs | pprint }}
+
+{% endblock %} \ No newline at end of file diff --git a/templates/images.html b/templates/images.html new file mode 100644 index 0000000..cbd3528 --- /dev/null +++ b/templates/images.html @@ -0,0 +1,61 @@ +{% extends "layout.html" %} +{% block body %} + + +

Dashboard » {{ client_name }} » Images

+ + +Dangling Images +All Images + +
COMPOSE PROJECTCONTAINER IDNAMECOMMANDCREATEDSTATUSPORTSIMAGE
+ {% if container.attrs.Config.Labels|length and container.attrs.Config.Labels['com.docker.compose.config-hash'] %} + {{ container.attrs.Config.Labels['com.docker.compose.project'] }} + {% endif%} + {{ container.short_id }}{{ container.name }}{{ container.attrs['Config']['Entrypoint'][0] }}{{ container.attrs['Created'][0:19] }}{{ container.status }} + {% for port in container.attrs['NetworkSettings']['Ports'] %} + {{ port }} + {% if container.attrs['NetworkSettings']['Ports'][port] is not none %} + + {% if container.attrs['NetworkSettings']['Ports'][port] is not none and + container.attrs['NetworkSettings']['Ports'][port]|length > 0 %} + {% for port_exposed in container.attrs['NetworkSettings']['Ports'][port] %} + ->{{ port_exposed['HostIp'] }}:{{ port_exposed['HostPort'] }} + + {% endfor %} + {% endif %} + {% endif %} + {% endfor %} + {{ container.attrs['Config']['Image'] }} + log + export + stop + remove + {% if container.status == 'running' %} + top + {% endif %} +
+ no containers +
+ + + + + + + + + + + {% for image in images %} + + + + + + + + + + {% else %} + + + + + {% endfor %} + + + +
REPOSITORYTAGIMAGE IDCREATEDSIZE
+ {% if image.attrs['RepoTags']|length > 0 %} + {{ image.attrs['RepoTags'][0].split(':')[0] }} + {% endif %} + + {% if image.attrs['RepoTags']|length > 0 %} + + {{ image.attrs['RepoTags'][0].split(':')[1] }} + {% endif %} + {{ image.attrs['Id'][7:] }}{{ image.attrs['Created'][0:19] }}{{ image.attrs['Size']|file_size }} + {% if image.attrs['RepoTags']|length %} + export + remove + {% else %} + remove + {% endif %} +
+ no images +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..e278ce1 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,114 @@ +{% extends "layout.html" %} +{% block body %} + +

Dashboard

+ + + + + {% for docker in data %} + + + + {% endfor %} + + +
+ + +

{{ docker.client_name }}

+

Docker Info

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
All Containers:{{ docker['info'].Containers }}
Containers Paused:{{ docker['info'].ContainersPaused }}
Containers Running:{{ docker['info'].ContainersRunning }}
Containers Stopped:{{ docker['info'].ContainersStopped }}
Images:{{ docker['info'].Images }}
Volumes:{{ docker.volumes }}
Networks:{{ docker.networks }}
Architecture:{{ docker['info'].Architecture }}
Server Version:{{ docker['info'].ServerVersion }}
System Time:{{ docker['info'].SystemTime[0:19] }}
+ + +

Docker Version

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Api Version:{{ docker['version'].ApiVersion }}
Architecture:{{ docker['version'].Arch }}
Build Time:{{ docker['version'].BuildTime[0:19] }}
Experimental:{{ docker['version'].Experimental }}
Git Commit:{{ docker['version'].GitCommit }}
Go Version:{{ docker['version'].GoVersion }}
KernelVersion:{{ docker['version'].KernelVersion }}
MinAPIVersion:{{ docker['version'].MinAPIVersion }}
Os:{{ docker['version'].Os }}
Version:{{ docker['version'].Version }}
+ +
+ + +{% endblock %} + diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..ff1c29a --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,63 @@ + + + Docker Manager + + + + + +
+ + {% with flashes = get_flashed_messages() %} + {% if flashes %} +
    + {% for message in flashes %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + + {% block body %}{% endblock %} +
+ + + + + diff --git a/templates/log.html b/templates/log.html new file mode 100644 index 0000000..7150fd2 --- /dev/null +++ b/templates/log.html @@ -0,0 +1,13 @@ +{% extends "layout.html" %} +{% block body %} + +

Dashboard » {{ client_name }} » Containers » {{ container.name }} logs

+ +Log with timestamps +Log without timestamps + +
+{{ logs|safe }} +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/networks.html b/templates/networks.html new file mode 100644 index 0000000..38d86c3 --- /dev/null +++ b/templates/networks.html @@ -0,0 +1,52 @@ +{% extends "layout.html" %} +{% block body %} + + +

Dashboard » {{ client_name }} » Networks

+ + + + + + + + + + + + + + + + {% for network in networks %} + + + + + + + + + + + {% else %} + + + + + {% endfor %} + + + +
DRIVERLABELSMOUNTPOINTNAMEOPTIONSSCOPE
+ +
+            {{ network.attrs|pprint }}
+    
+
{{ network.attrs['Driver'] }} {{ network.attrs['Labels'] }} {{ network.attrs['Mountpoint'] }} {{ network.attrs['Name'] }} {{ network.attrs['Options'] }} {{ network.attrs['Scope'] }}
+ no networks +
+ +{% endblock %} + + diff --git a/templates/top.html b/templates/top.html new file mode 100644 index 0000000..9d581e6 --- /dev/null +++ b/templates/top.html @@ -0,0 +1,28 @@ +{% extends "layout.html" %} +{% block body %} + +

Dashboard » {{ client_name }} » Containers » {{ container.name }} + processes

+ + + + + + {% for header in top['Titles'] %} + + {% endfor %} + + + + {% for processes in top['Processes'] %} + + {% for process in processes %} + + {% endfor %} + + {% endfor %} + +
{{ header }}
{{ process }}
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/volumes.html b/templates/volumes.html new file mode 100644 index 0000000..90efb23 --- /dev/null +++ b/templates/volumes.html @@ -0,0 +1,48 @@ +{% extends "layout.html" %} +{% block body %} + + +

Dashboard » {{ client_name }} » Volumes

+ + +Prune Unused Volumes + + + + + + + + + + + + + + + {% for volume in volumes %} + + + + + + + + + + {% else %} + + + + + {% endfor %} + + + +
DRIVERLABELSMOUNTPOINTNAMEOPTIONSSCOPE
{{ volume.attrs['Driver'] }} {{ volume.attrs['Labels'] }} {{ volume.attrs['Mountpoint'] }} {{ volume.attrs['Name'] }} {{ volume.attrs['Options'] }} {{ volume.attrs['Scope'] }}
+ no images +
+ +{% endblock %} + +