Initial commit of docker flask.

This commit is contained in:
Krzysztof Płaczek
2018-03-09 10:33:19 +01:00
commit 6e9b3aed3a
16 changed files with 1128 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/.idea
/__pycache__

10
images.py Normal file
View File

@@ -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)

384
index.py Normal file
View File

@@ -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('/<client_name>/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('/<client_name>/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('/<client_name>/images/id/<image_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('/<client_name>/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('/<client_name>/images/export/<image_tag>')
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('/<client_name>/images/remove/<image_tag>')
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('/<client_name>/images/remove_by_id/<image_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('/<client_name>/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('/<client_name>/containers/<compose_project>')
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('/<client_name>/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('/<client_name>/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('/<client_name>/containers/id/<short_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('/<client_name>/containers/log/<short_id>', defaults={"timestamp": 0})
@app.route('/<client_name>/containers/log/<short_id>/<int:timestamp>')
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("<br>", '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('/<client_name>/containers/top/<short_id>')
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('/<client_name>/containers/export/<short_id>')
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('/<client_name>/containers/remove/<short_id>')
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('/<client_name>/containers/stop/<short_id>')
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('/<client_name>/containers/start/<short_id>')
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('/<client_name>/containers/restart/<short_id>')
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('/<client_name>/containers/<status>')
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('/<client_name>/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('/<client_name>/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('/<client_name>/volumes/id/<short_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('/<client_name>/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)

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

113
static/style.css Normal file
View File

@@ -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; }

17
templates/container.html Normal file
View File

@@ -0,0 +1,17 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; <a href="/{{ client_name }}/containers">Containers</a> &raquo; {{ container.name }}</h1>
<form action="{{ url_for('rename_container', client_name=client_name) }}" method="post">
<p><input type="text" name="name" size="60" value="{{ container.name }}"><!--
--><input type="hidden" name="short_id" value="{{ container.short_id }}"><!--
--><input type="submit" value="rename">
</form>
<h2>Details</h2>
<pre>
{{ container.attrs|pprint }}
</pre>
{% endblock %}

108
templates/containers.html Normal file
View File

@@ -0,0 +1,108 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; Containers</h1>
<a class="button--link" href="/{{ client_name }}/containers/all">All Containers</a>
<a class="button--link" href="/{{ client_name }}/containers/paused">Paused Containers</a>
<a class="button--link" href="/{{ client_name }}/containers">Running Containers</a>
<a class="button--link" href="/{{ client_name }}/containers/exited">Stopped Containers</a>
<input type="text" class="search_table" placeholder="search">
<h2>{{ currentComposerProject }}</h2>
{% 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']) %}
<h2>
<a href="{{ url_for('containers_compose_action', client_name=client_name, compose_project=container.attrs.Config.Labels['com.docker.compose.project']) }}">{{
container.attrs.Config.Labels['com.docker.compose.project'] }}</a></h2>
{% endif%}
<!--<table>-->
<!--<thead>-->
<!--<tr>-->
<!--<th>COMPOSE PROJECT</th>-->
<!--<th>CONTAINER ID</th>-->
<!--<th>NAME</th>-->
<!--<th>COMMAND</th>-->
<!--<th>CREATED</th>-->
<!--<th>STATUS</th>-->
<!--<th>PORTS</th>-->
<!--<th>IMAGE</th>-->
<!--</tr>-->
<!--</thead>-->
<!--<tbody>-->
<div class="details">
<div>
<div class="details__status details__status--{{ container.status }}">{{ container.status }}</div>
<div class="details__name"><a class="button--link"
href="/{{ client_name }}/containers/id/{{ container.short_id }}">{{
container.name
}}</a></div>
<div class="details__id"><span title="{{ container.id }}">ShortID: {{ container.short_id }}</span></div>
</div>
<div>
<div class="details__created">{{ container.attrs['Created'][0:19] }}</div>
<div class="details__entrypoint">{{ container.attrs['Config']['Entrypoint'][0] }}</div>
<div class="details__image">{{ container.attrs['Config']['Image'] }}</div>
</div>
<div class="details__more-info">
<div class="more-info__ports">
{% 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 %}
</div>
</div>
<div>
</div>
<div class="details__controll">
<a class="button--link" href="/{{ client_name }}/containers/log/{{ container.short_id }}">log</a>
<a class="button--link" href="/{{ client_name }}/containers/export/{{ container.short_id }}">export</a>
{% if (container.status == 'exited') or (container.status == 'created') %}
<a class="button--link" href="/{{ client_name }}/containers/start/{{ container.short_id }}">start</a>
<a class="button--link" href="/{{ client_name }}/containers/remove/{{ container.short_id }}">remove</a>
{% endif %}
{% if container.status == 'running' %}
<a class="button--link" href="/{{ client_name }}/containers/stop/{{ container.short_id }}">stop</a>
<a class="button--link" href="/{{ client_name }}/containers/restart/{{ container.short_id }}">restart</a>
<a class="button--link" href="/{{ client_name }}/containers/top/{{ container.short_id }}">top</a>
{% endif %}
</div>
</div>
{% else %}
<div>
<div>
no containers
</div>
</div>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,103 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; Containers</h1>
<a class="button--link" href="/{{ client_name }}/containers/all">All Containers</a>
<a class="button--link" href="/{{ client_name }}/containers/paused">Paused Containers</a>
<a class="button--link" href="/{{ client_name }}/containers">Running Containers</a>
<a class="button--link" href="/{{ client_name }}/containers/exited">Stopped Containers</a>
<input type="text" class="search_table" placeholder="search">
<h2>{{ currentComposerProject }}</h2>
{% 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'])%}
</tbody>
</table>
{% endif %}
<h2>{{ container.attrs.Config.Labels['com.docker.compose.project'] }}</h2>
<table>
<thead>
<tr>
<th>COMPOSE PROJECT</th>
<th>CONTAINER ID</th>
<th>NAME</th>
<th>COMMAND</th>
<th>CREATED</th>
<th>STATUS</th>
<th>PORTS</th>
<th>IMAGE</th>
</tr>
</thead>
<tbody>
{% endif%}
<tr>
<td>
{% if container.attrs.Config.Labels|length and container.attrs.Config.Labels['com.docker.compose.config-hash'] %}
<a href="{{ url_for('containers_compose_action', client_name=client_name, compose_project=container.attrs.Config.Labels['com.docker.compose.project']) }}">{{ container.attrs.Config.Labels['com.docker.compose.project'] }}</a>
{% endif%}
</td>
<td><span title="{{ container.id }}">{{ container.short_id }}</span></td>
<td><a href="/{{ client_name }}/containers/id/{{ container.short_id }}">{{ container.name }}</a></td>
<td>{{ container.attrs['Config']['Entrypoint'][0] }}</td>
<td>{{ container.attrs['Created'][0:19] }}</td>
<td>{{ container.status }}</td>
<td>
{% 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 %}
</td>
<td>{{ container.attrs['Config']['Image'] }}</td>
<td>
<a href="/{{ client_name }}/containers/log/{{ container.short_id }}">log</a>
<a href="/{{ client_name }}/containers/export/{{ container.short_id }}">export</a>
<a href="/{{ client_name }}/containers/stop/{{ container.short_id }}">stop</a>
<a href="/{{ client_name }}/containers/remove/{{ container.short_id }}">remove</a>
{% if container.status == 'running' %}
<a href="/{{ client_name }}/containers/top/{{ container.short_id }}">top</a>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="9">
no containers
</td>
</tr>
{% endfor %}
{% endblock %}

12
templates/image.html Normal file
View File

@@ -0,0 +1,12 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; <a href="/{{ client_name }}/images">Images</a>
&raquo; {% if image.attrs['RepoTags']|length %} {{ image.attrs['RepoTags'][0] }} {% else %} {{ image.attrs['Id'][7:]
}} {% endif %}</h1>
<h2>Details</h2>
<pre>
{{ image.attrs | pprint }}
</pre>
{% endblock %}

61
templates/images.html Normal file
View File

@@ -0,0 +1,61 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; Images</h1>
<a class="button--link" href="/{{ client_name }}/images/dangling">Dangling Images</a>
<a class="button--link" href="/{{ client_name }}/images/all">All Images</a>
<input type="text" class="search_table" placeholder="search">
<table>
<thead>
<tr>
<th>REPOSITORY</th>
<th>TAG</th>
<th>IMAGE ID</th>
<th>CREATED</th>
<th>SIZE</th>
</tr>
</thead>
<tbody>
{% for image in images %}
<tr>
<td>
{% if image.attrs['RepoTags']|length > 0 %}
<a href="{{ url_for('image_action', client_name=client_name, image_id=image.attrs['Id'][7:]) }}">{{ image.attrs['RepoTags'][0].split(':')[0] }} </a>
{% endif %}
</td>
<td>
{% if image.attrs['RepoTags']|length > 0 %}
{{ image.attrs['RepoTags'][0].split(':')[1] }}
{% endif %}
</td>
<td>{{ image.attrs['Id'][7:] }}</td>
<td>{{ image.attrs['Created'][0:19] }}</td>
<td>{{ image.attrs['Size']|file_size }}</td>
<td>
{% if image.attrs['RepoTags']|length %}
<a href="{{ url_for('export_image_action', client_name=client_name, image_tag=image.attrs['RepoTags'][0]|encode64 )}}">export</a>
<a href="{{ url_for('remove_image_action', client_name=client_name, image_tag=image.attrs['RepoTags'][0]|encode64 )}}">remove</a>
{% else %}
<a href="{{ url_for('remove_imageby_id_action', client_name=client_name, image_id=image.attrs['Id'][7:] )}}">remove</a>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="9">
no images
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

114
templates/index.html Normal file
View File

@@ -0,0 +1,114 @@
{% extends "layout.html" %}
{% block body %}
<h1>Dashboard</h1>
<table>
<tr>
{% for docker in data %}
<td>
<h2>{{ docker.client_name }}</h2>
<h3>Docker Info</h3>
<table>
<tr>
<td>All Containers:</td>
<td><a href="/{{ docker.client_name }}/containers/all">{{ docker['info'].Containers }}</a></td>
</tr>
<tr>
<td>Containers Paused:</td>
<td><a href="/{{ docker.client_name }}/containers/paused">{{ docker['info'].ContainersPaused }}</a></td>
</tr>
<tr>
<td>Containers Running:</td>
<td><a href="/{{ docker.client_name }}/containers">{{ docker['info'].ContainersRunning }}</a></td>
</tr>
<tr>
<td>Containers Stopped:</td>
<td><a href="/{{ docker.client_name }}/containers/exited">{{ docker['info'].ContainersStopped }}</a></td>
</tr>
<tr>
<td>Images:</td>
<td><a href="/{{ docker.client_name }}/images">{{ docker['info'].Images }}</a></td>
</tr>
<tr>
<td>Volumes:</td>
<td><a href="{{ url_for('volumes_action', client_name=docker.client_name) }}">{{ docker.volumes }}</a></td>
</tr>
<tr>
<td>Networks:</td>
<td><a href="{{ url_for('networks_action', client_name=docker.client_name) }}">{{ docker.networks }}</a></td>
</tr>
<tr>
<td>Architecture:</td>
<td>{{ docker['info'].Architecture }}</td>
</tr>
<tr>
<td>Server Version:</td>
<td>{{ docker['info'].ServerVersion }}</td>
</tr>
<tr>
<td>System Time:</td>
<td>{{ docker['info'].SystemTime[0:19] }}</td>
</tr>
</table>
<h3>Docker Version</h3>
<table>
<tr>
<td>Api Version:</td>
<td>{{ docker['version'].ApiVersion }}</td>
</tr>
<tr>
<td>Architecture:</td>
<td>{{ docker['version'].Arch }}</td>
</tr>
<tr>
<td>Build Time:</td>
<td>{{ docker['version'].BuildTime[0:19] }}</td>
</tr>
<tr>
<td>Experimental:</td>
<td>{{ docker['version'].Experimental }}</td>
</tr>
<tr>
<td>Git Commit:</td>
<td><a target="_blank" href="https://github.com/docker/docker-ce/commit/{{ docker['version'].GitCommit }}">{{ docker['version'].GitCommit }}</a></td>
</tr>
<tr>
<td>Go Version:</td>
<td>{{ docker['version'].GoVersion }}</td>
</tr>
<tr>
<td>KernelVersion:</td>
<td>{{ docker['version'].KernelVersion }}</td>
</tr>
<tr>
<td>MinAPIVersion:</td>
<td>{{ docker['version'].MinAPIVersion }}</td>
</tr>
<tr>
<td>Os:</td>
<td>{{ docker['version'].Os }}</td>
</tr>
<tr>
<td>Version:</td>
<td>{{ docker['version'].Version }}</td>
</tr>
</table>
</td>
{% endfor %}
</tr>
</table>
{% endblock %}

63
templates/layout.html Normal file
View File

@@ -0,0 +1,63 @@
<!doctype html>
<head>
<title>Docker Manager</title>
<link href="https://fonts.googleapis.com/css?family=Ubuntu&amp;subset=latin-ext" rel="stylesheet">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}"/>
</head>
<body>
<div class="container">
{% with flashes = get_flashed_messages() %}
{% if flashes %}
<ul class="flashes">
{% for message in flashes %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
</div>
<script>
window.onload = load;
function load() {
if(!!document.querySelector('input.search_table')){
document.querySelector('input.search_table').addEventListener('keyup', searchTable);
}
function searchTable(){
value = this.value;
document.querySelectorAll('table tbody tr').forEach(function(element){
if(element.innerText.indexOf(value) == -1){
element.style.display = 'none';
} else {
element.style.display = 'table-row';
}
})
document.querySelectorAll('.details').forEach(function(element){
if(element.innerText.indexOf(value) == -1){
element.style.display = 'none';
} else {
element.style.display = 'block';
}
})
}
if(!!document.querySelector('.logs')){
document.querySelector('.logs').scrollTop = 10e6;
}
}
</script>
</body>
</html>

13
templates/log.html Normal file
View File

@@ -0,0 +1,13 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; <a href="/{{ client_name }}/containers">Containers</a> &raquo; {{ container.name }} logs</h1>
<a class="button--link" href="{{ url_for('log_action', client_name=client_name, short_id=short_id, timestamp=1) }}">Log with timestamps</a>
<a class="button--link" href="{{ url_for('log_action', client_name=client_name, short_id=short_id, timestamp=0) }}">Log without timestamps </a>
<div class="logs">
{{ logs|safe }}
</div>
{% endblock %}

52
templates/networks.html Normal file
View File

@@ -0,0 +1,52 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; Networks</h1>
<input type="text" class="search_table" placeholder="search">
<table>
<thead>
<tr>
<th>DRIVER</th>
<th>LABELS</th>
<th>MOUNTPOINT</th>
<th>NAME</th>
<th>OPTIONS</th>
<th>SCOPE</th>
</tr>
</thead>
<tbody>
{% for network in networks %}
<tr>
<td>
<pre>
{{ network.attrs|pprint }}
</pre>
</td>
<td> {{ network.attrs['Driver'] }}</td>
<td> {{ network.attrs['Labels'] }}</td>
<td> {{ network.attrs['Mountpoint'] }}</td>
<td> {{ network.attrs['Name'] }}</td>
<td> {{ network.attrs['Options'] }}</td>
<td> {{ network.attrs['Scope'] }}</td>
</tr>
{% else %}
<tr>
<td colspan="6">
no networks
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

28
templates/top.html Normal file
View File

@@ -0,0 +1,28 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; <a href="/{{ client_name }}/containers">Containers</a> &raquo; {{ container.name }}
processes</h1>
<input type="text" class="search_table" placeholder="search">
<table>
<thead>
<tr>
{% for header in top['Titles'] %}
<th>{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for processes in top['Processes'] %}
<tr>
{% for process in processes %}
<td>{{ process }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

48
templates/volumes.html Normal file
View File

@@ -0,0 +1,48 @@
{% extends "layout.html" %}
{% block body %}
<h1><a href="/">Dashboard</a> &raquo; {{ client_name }} &raquo; Volumes</h1>
<a class="button--link" href="{{ url_for('volumes_prune_action', client_name=client_name) }}">Prune Unused Volumes</a>
<input type="text" class="search_table" placeholder="search">
<table>
<thead>
<tr>
<th>DRIVER</th>
<th>LABELS</th>
<th>MOUNTPOINT</th>
<th>NAME</th>
<th>OPTIONS</th>
<th>SCOPE</th>
</tr>
</thead>
<tbody>
{% for volume in volumes %}
<tr>
<td> {{ volume.attrs['Driver'] }}</td>
<td> {{ volume.attrs['Labels'] }}</td>
<td> {{ volume.attrs['Mountpoint'] }}</td>
<td> {{ volume.attrs['Name'] }}</td>
<td> {{ volume.attrs['Options'] }}</td>
<td> {{ volume.attrs['Scope'] }}</td>
</tr>
{% else %}
<tr>
<td colspan="6">
no images
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}