From 6e9b3aed3a3a55597087e4b76d2501b9a8302374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20P=C5=82aczek?= Date: Fri, 9 Mar 2018 10:33:19 +0100 Subject: [PATCH] Initial commit of docker flask. --- .gitignore | 2 + images.py | 10 + index.py | 384 ++++++++++++++++++++++++++++++++ static/favicon.ico | Bin 0 -> 149174 bytes static/style.css | 113 ++++++++++ templates/container.html | 17 ++ templates/containers.html | 108 +++++++++ templates/containers.table.html | 103 +++++++++ templates/image.html | 12 + templates/images.html | 61 +++++ templates/index.html | 114 ++++++++++ templates/layout.html | 63 ++++++ templates/log.html | 13 ++ templates/networks.html | 52 +++++ templates/top.html | 28 +++ templates/volumes.html | 48 ++++ 16 files changed, 1128 insertions(+) create mode 100644 .gitignore create mode 100644 images.py create mode 100644 index.py create mode 100644 static/favicon.ico create mode 100644 static/style.css create mode 100644 templates/container.html create mode 100644 templates/containers.html create mode 100644 templates/containers.table.html create mode 100644 templates/image.html create mode 100644 templates/images.html create mode 100644 templates/index.html create mode 100644 templates/layout.html create mode 100644 templates/log.html create mode 100644 templates/networks.html create mode 100644 templates/top.html create mode 100644 templates/volumes.html 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 0000000000000000000000000000000000000000..ce2e6a6f375f035a42cb080961cb23ded019508e GIT binary patch literal 149174 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 GB=CPr-M}jV literal 0 HcmV?d00001 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 %} + +