Initial commit of docker flask.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/.idea
|
||||
/__pycache__
|
||||
10
images.py
Normal file
10
images.py
Normal 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
384
index.py
Normal 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
BIN
static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
113
static/style.css
Normal file
113
static/style.css
Normal 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
17
templates/container.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » <a href="/{{ client_name }}/containers">Containers</a> » {{ 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
108
templates/containers.html
Normal file
@@ -0,0 +1,108 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » 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 %}
|
||||
103
templates/containers.table.html
Normal file
103
templates/containers.table.html
Normal file
@@ -0,0 +1,103 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » 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
12
templates/image.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » <a href="/{{ client_name }}/images">Images</a>
|
||||
» {% 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
61
templates/images.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » 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
114
templates/index.html
Normal 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
63
templates/layout.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<title>Docker Manager</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Ubuntu&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
13
templates/log.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » <a href="/{{ client_name }}/containers">Containers</a> » {{ 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
52
templates/networks.html
Normal file
@@ -0,0 +1,52 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » 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
28
templates/top.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » <a href="/{{ client_name }}/containers">Containers</a> » {{ 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
48
templates/volumes.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
|
||||
|
||||
<h1><a href="/">Dashboard</a> » {{ client_name }} » 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 %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user