Major improvements incl. CSS

This commit is contained in:
Henri
2020-11-26 23:21:14 +01:00
parent 3cde7d371d
commit 16ce040ab9
14 changed files with 642 additions and 145 deletions

View File

@@ -1,15 +1,20 @@
<html>
<head>
<meta http-equiv='content-type' content='text/html; charset=utf-8' />
<title>Towalink WireGuard Frontend</title>
<link rel='stylesheet' media='screen' href='/static/layout.css' />
</head>
<body>
<div class='container'>
<div class='header'>
{% include 'part_header.html' %}
</div>
<div class='content'>{% block content %}{% endblock %}</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Towalink WireGuard Frontend</title>
<link rel="stylesheet" media="screen" href="/static/styles.css" />
</head>
<body>
{% include 'part_header.html' %}
<section>
<div class="content">
{%- block content %}{% endblock %}
</div>
</body>
</section>
<footer>
<a href="https://github.com/towalink/wgfrontend/" target="_blank">Powered by the open source &quot;Towalink WireGuard Frontend&quot;</a>
</footer>
</body>
</html>

View File

@@ -1,24 +1,29 @@
{% extends 'base.html' %}
{% block content %}
<h3>Client Config</h3>
<div class='instructions'>
<a href='https://www.wireguard.com/install/' target="_blank">WireGuard client installation instructions</a>
</div>
<div class='span-18 last'>
<form method="get" action="download">
<table>
<tr>
<td>
{{ peerdata['Description'] }}<br>
{{ peerdata['Address'] }}
</td>
<td>
<img src="/configs/{{ peerdata['Id'] }}.png" alt="QR Code">
<button type="submit" name="id" value="{{ peerdata['Id'] }}">Download Config</button>
<button type="submit" formaction=".." }}">Return</button>
</td>
</tr>
</table>
</form>
</div>
<h3>Client Config</h3>
<div class='form'>
<form method="get" action="download">
<div class="buttonrow">
<button class="button buttonhighlight" type="submit" name="action" value="list" formaction="..">Return to List</button>
</div>
<div class="table">
<div class="table-row">
<div class="table-cell bordertop">
{{ peerdata['Description'] }}<br>
<small>{{ peerdata['Address'] }}</small><br>
</div>
<div class="table-cell twobuttoncell bordertop2">
<button class="button" type="submit" name="id" value="{{ peerdata['Id'] }}" formaction="edit">Edit Client</button>
<button class="button" type="submit" name="id" value="{{ peerdata['Id'] }}" formaction="download">Download Config</button>
</div>
</div>
<div class="table-row" style="text-align: center;">
<img class="qrcode" src="/configs/{{ peerdata['Id'] }}.png" alt="QR Code">
</div>
</div>
</form>
</div>
<div class='instructions'>
See the <a href='https://www.wireguard.com/install/' target="_blank">WireGuard client installation instructions</a>.
</div>
{% endblock %}

View File

@@ -1,22 +1,28 @@
{% extends 'base.html' %}
{% block content %}
<h3>Configured Clients</h3>
<div class='span-18 last'>
<form method="get" action="edit">
<table>
<tr>
<td>
<input type="hidden" name="id" value="{{ peerdata['Id'] }}" />
<input type="text" name="description" value="{{ peerdata['Description'] }}" size="40" /><br>
{{ peerdata['Address'] }}
</td>
<td>
<button type="submit" name="action" value="save">Save Data</button>
<button type="submit" name="action" value="delete" formaction="/" onclick="return confirm('Do you really want to delete this client?')">Delete Client</button>
<button type="submit" formaction="/">Return to List</button>
</td>
</tr>
</table>
</form>
</div>
<h3>{% if peerdata['Id'] %}Edit{% else %}New{% endif %} Client</h3>
<div class='form'>
<form method="get" action="..">
<div class="buttonrow">
<button class="button buttonhighlight" type="submit" name="action" value="list">Return to List</button>
</div>
<div class="table">
<div class="table-row">
<div class="table-cell bordertop">
<input type="hidden" name="id" value="{{ peerdata['Id'] }}" />
<input class="inputtext" type="text" name="description" value="{{ peerdata['Description'] }}" size="40" /><br>
<small>{{ peerdata['Address'] }}</small>
</div>
<div class="table-cell twobuttoncell bordertop2">
<button class="button" type="submit" name="action" value="save" formaction="config">{% if peerdata['Id'] %}Save Changes{% else %}Save{%endif %}</button>
{%- if peerdata['Id'] %}
<button class="button" type="submit" name="action" value="delete" onclick="return confirm('Do you really want to delete this client?')">Delete Client</button>
{%- else %}
<button class="button" type="submit" name="action" value="list")">Cancel</button>
{%- endif %}
</div>
</div>
</div>
</form>
</div>
{% endblock %}

View File

@@ -1,23 +1,35 @@
{% extends 'base.html' %}
{% block content %}
<h3>Configured Clients</h3>
<div class='span-18 last'>
<form method="get" action="edit">
<button type="submit" name="action" value="new">Add Client</button>
<table>
{%- for peer, peerdata in peers.items()|sort(attribute='1.Description') %}
<tr>
<td>
{{ peerdata['Description'] }}<br>
{{ peerdata['Address'] }}
</td>
<td>
<button type="submit" name="id" value="{{ peerdata['Id'] }}">Edit Client</button>
<button type="submit" name="id" value="{{ peerdata['Id'] }}" formaction="config">Get Config</button>
</td>
</tr>
{%- endfor %}
</table>
</form>
</div>
{% endblock %}
<h3>Configured Clients</h3>
<div class='form'>
<form method="get" action="edit">
<div class="buttonrow">
<button class="button buttonhighlight" type="submit" name="action" value="new">Add Client</button>
</div>
<div class="table">
{%- for peer, peerdata in peers.items()|sort(attribute='1.Description') %}
<div class="line"></div>
<div class="table-row">
<div class="table-cell bordertop">
{{ peerdata['Description'] }}<br>
<small>{{ peerdata['Address'] }}</small>
</div>
<div class="table-cell twobuttoncell bordertop2">
<button class="button" type="submit" name="id" value="{{ peerdata['Id'] }}">Edit Client</button>
<button class="button" type="submit" name="id" value="{{ peerdata['Id'] }}" formaction="config">Get Config</button>
</div>
</div>
{%- endfor %}
{% if not peers %}
<div class="table">
<div class="line"></div>
<div class="table-row">
<div class="table-cell bordertop">
There is no client configured up to now.
</div>
</div>
{% endif %}
</div>
</form>
</div>
{% endblock %}

39
src/templates/login.html Normal file
View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Towalink WireGuard Frontend - Login</title>
<link rel="stylesheet" media="screen" href="/static/styles.css" />
</head>
<body>
<div class="loginform">
<form method="post" action="do_login">
<div class=loginerror>{{ error_msg }}</div>
<table>
<tr>
<td>
User:
</td>
<td>
<input class="logininput" type="text" name="username" value="{{ username }}" size="40" />
</td>
<tr>
<td>
Password:&nbsp;&nbsp;
</td>
<td>
<input class="logininput" type="password" name="password" size="40" />
<input type="hidden" name="from_page" value="{{ from_page }}" />
</td>
</tr>
<tr>
<td colspan=2 align=right>
<input class="button loginmargin" type="submit" value="Login" />
</td>
</tr>
</table>
</form>
</div>
</body>
</html>

View File

@@ -1,3 +1,9 @@
<div class='heading'>
<h2><a href='/'>Towalink WireGuard Frontend</a></h2>
</div>
<header>
<h2><a href="/"><img src="static/logo.svg" alt="Towalink logo"><span>Towalink WireGuard Frontend</span></a></h2>
<div class="user">
<p>{% if sessiondata['username'] %}{{ sessiondata['username'] }}{% else %}not logged in{%endif %}</p>
<form action="/logout">
<input class="button" type="submit" value="Logout" />
</form>
</div>
</header>

View File

@@ -12,49 +12,12 @@ import pwdtools
import wgcfg
def login_screen(from_page='..', username='', error_msg='', **kwargs):
"""Based on https://docs.cherrypy.org/en/latest/_modules/cherrypy/lib/cptools.html"""
content="""
<form method="post" action="do_login">
<div align=center>
<span class=errormsg>%s</span>
<table>
<tr>
<td>
Login:
</td>
<td>
<input type="text" name="username" value="%s" size="40" />
</td>
<tr>
<td>
Password:
</td>
<td>
<input type="password" name="password" size="40" />
<input type="hidden" name="from_page" value="%s" />
</td>
</tr>
<tr>
<td colspan=2 align=right>
<input type="submit" value="Login" />
</td>
</tr>
</table>
</div>
</form>
""" % (error_msg, username, from_page)
title='Login'
return ('<html><body>' + content + '</body></html>').encode('utf-8')
# return cherrypy.tools.encode('<html><body>' + content + '</body></html>')
class WebApp():
def __init__(self, cfg):
"""Instance initialization"""
self.cfg = cfg
self.jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'))
self.jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')))
self.wg = wgcfg.WGCfg(self.cfg.wg_configfile, self.cfg.libdir)
@cherrypy.expose
@@ -64,13 +27,21 @@ class WebApp():
self.wg.delete_peer(peer)
peers = self.wg.get_peers()
tmpl = self.jinja_env.get_template('index.html')
return tmpl.render(peers=peers)
return tmpl.render(sessiondata=cherrypy.session, peers=peers)
@cherrypy.expose
def config(self, id):
peer, peerdata = self.wg.get_peer_byid(id)
def config(self, action=None, id=None, description=None):
peerdata = None
if (action == 'save') and id:
peer, peerdata = self.wg.get_peer_byid(id)
peerdata = self.wg.update_peer(peer, description)
if (action == 'save') and not id:
peer = self.wg.create_peer(description)
peerdata = self.wg.get_peer(peer)
if not peerdata:
peer, peerdata = self.wg.get_peer_byid(id)
tmpl = self.jinja_env.get_template('config.html')
return tmpl.render(peerdata=peerdata)
return tmpl.render(sessiondata=cherrypy.session, peerdata=peerdata)
@cherrypy.expose
def edit(self, action='edit', id=None, description=None):
@@ -84,43 +55,52 @@ class WebApp():
if action == 'new': # default values for new client
peerdata = { 'Description': description, 'Id': '' }
else: # save changes
peer = self.wg.create_peer(description)
peerdata = self.wg.get_peer(peer)
raise ValueError()
tmpl = self.jinja_env.get_template('edit.html')
return tmpl.render(peerdata=peerdata)
return tmpl.render(sessiondata=cherrypy.session, peerdata=peerdata)
@cherrypy.expose
def download(self, id):
"""Provide the WireGuard config for the client with the given identifier for download"""
peer, peerdata = self.wg.get_peer_byid(id)
config, peerdata = self.wg.get_peerconfig(peer)
cherrypy.response.headers['Content-Disposition'] = f'attachment; filename=wg_{id}.conf'
cherrypy.response.headers['Content-Type'] = 'text/plain' # 'application/x-download' 'application/octet-stream'
return config.encode('utf-8')
@cherrypy.expose
def logout(self):
username = cherrypy.session['username']
cherrypy.session.clear()
return '"{0}" has been logged out'.format(username)
def run_webapp(cfg):
def check_username_and_password(username, password):
def check_username_and_password(self, username, password):
"""Check whether provided username and password are valid when authenticating"""
if (username in cfg.users) and (pwdtools.verify_password(cfg.users[username], password)):
if (username in self.cfg.users) and (pwdtools.verify_password(self.cfg.users[username], password)):
return
return 'invalid username/password'
def login_screen(self, from_page='..', username='', error_msg='', **kwargs):
"""Shows a login form"""
tmpl = self.jinja_env.get_template('login.html')
return tmpl.render(from_page=from_page, username=username, error_msg=error_msg).encode('utf-8')
@cherrypy.expose
def logout(self):
username = cherrypy.session['username']
cherrypy.session.clear()
cherrypy.response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
cherrypy.response.headers['Pragma'] = 'no-cache'
cherrypy.response.headers['Expires'] = '0'
raise cherrypy.HTTPRedirect('/', 302)
return '"{0}" has been logged out'.format(username)
def run_webapp(cfg):
"""Runs the CherryPy web application with the provided configuration data"""
script_path = os.path.dirname(os.path.abspath(__file__))
conf = {
app = WebApp(cfg)
app_conf = {
'/': {
'tools.sessions.on': True,
'tools.staticdir.root': os.path.join(script_path, 'webroot'),
# 'tools.session_auth.on': True,
'tools.session_auth.login_screen': login_screen,
'tools.session_auth.check_username_and_password': check_username_and_password,
'tools.session_auth.on': True,
'tools.session_auth.login_screen': app.login_screen,
'tools.session_auth.check_username_and_password': app.check_username_and_password,
},
'/configs': {
'tools.staticdir.on': True,
@@ -128,11 +108,13 @@ def run_webapp(cfg):
'tools.staticdir.dir': cfg.libdir
},
'/static': {
'tools.session_auth.on': False,
'tools.staticdir.on': True,
'tools.staticdir.dir': 'static'
},
'/favicon.ico':
{
'tools.session_auth.on': False,
'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.join(script_path, 'webroot', 'static', 'favicon.ico')
}
@@ -144,7 +126,7 @@ def run_webapp(cfg):
cherrypy.config.update({'server.socket_host': '0.0.0.0',
'server.socket_port': 8080,
})
cherrypy.quickstart(WebApp(cfg), '/', conf)
cherrypy.quickstart(app, '/', app_conf)
if __name__ == '__main__':

203
src/webroot/static/logo.svg Normal file
View File

@@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="35mm"
height="35mm"
viewBox="0 0 124.01573 124.01573"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="20200430_Logo_white.svg">
<defs
id="defs4">
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4527"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#0026ff;fill-opacity:1;fill-rule:evenodd;stroke:#0026ff;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4539"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6,-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-0"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4527-5"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#0026ff;fill-opacity:1;fill-rule:evenodd;stroke:#0026ff;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-0-6"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4527-5-6"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#0026ff;fill-opacity:1;fill-rule:evenodd;stroke:#0026ff;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-02"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4527-2"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#0026ff;fill-opacity:1;fill-rule:evenodd;stroke:#0026ff;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.310501"
inkscape:cx="182.65431"
inkscape:cy="138.66991"
inkscape:document-units="px"
inkscape:current-layer="g5661"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1033"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:window-maximized="1"
borderlayer="false" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-928.34647)">
<g
id="g5661">
<rect
y="261.19177"
x="207.31435"
height="71.413086"
width="276.99622"
id="rect4456"
style="opacity:1;fill:none;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
y="941.92017"
x="86.271614"
height="39.020161"
width="61.361115"
id="rect4476"
style="opacity:1;fill:none;fill-opacity:1;stroke:none;stroke-width:2.70799994;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
transform="matrix(0.99987663,-0.01570731,0.01570731,0.99987663,0,0)" />
<rect
y="946.04987"
x="56.911366"
height="43.763786"
width="61.973194"
id="rect4479"
style="opacity:1;fill:none;fill-opacity:1;stroke:none;stroke-width:2.70799994;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
transform="matrix(0.99987663,-0.01570731,0.01570731,0.99987663,0,0)" />
<rect
y="939.16394"
x="61.808002"
height="45.293987"
width="62.585274"
id="rect4482"
style="opacity:1;fill:none;fill-opacity:1;stroke:none;stroke-width:2.70799994;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
transform="matrix(0.99987663,-0.01570731,0.01570731,0.99987663,0,0)" />
<path
inkscape:transform-center-y="-16.366332"
inkscape:transform-center-x="0.37613837"
d="m 423.28484,447.29862 c 7.63877,-13.12302 -104.87801,16.59839 -91.75499,24.23716 13.12301,7.63877 -16.5984,-104.878 -24.23717,-91.75499 -7.63877,13.12302 104.878,-16.5984 91.75499,-24.23717 -13.12302,-7.63877 16.5984,104.87801 24.23717,91.755 z"
inkscape:randomized="0"
inkscape:rounded="-0.16"
inkscape:flatsided="true"
sodipodi:arg2="1.3125441"
sodipodi:arg1="0.52714594"
sodipodi:r2="34.365295"
sodipodi:r1="67.105957"
sodipodi:cy="413.5397"
sodipodi:cx="365.28876"
sodipodi:sides="4"
id="path5631"
style="opacity:1;fill:none;fill-opacity:1;stroke:#0026ff;stroke-width:6;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
sodipodi:type="star"
transform="matrix(0.99987664,-0.01570731,0.01570731,0.99987664,-309.89111,582.58709)" />
<circle
r="10.387358"
cy="991.18738"
cx="46.285023"
id="path5633"
style="opacity:1;fill:#0026ff;fill-opacity:1;stroke:#0026ff;stroke-width:6;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
transform="matrix(0.99987663,-0.01570731,0.01570731,0.99987663,0,0)" />
<path
inkscape:connector-curvature="0"
id="path5635"
d="m 7.8998124,959.82946 103.0679076,58.11564 0,0"
style="fill:none;fill-rule:evenodd;stroke:#0026ff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path5635-8"
d="m 89.387832,941.50106 -58.11557,103.06794 0,0"
style="fill:none;fill-rule:evenodd;stroke:#0026ff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,218 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font: normal 16px sans-serif;
background-color: #f3f3f3;
color: #000;
}
a {
text-decoration: none;
color: inherit;
cursor: pointer;
opacity: 0.9;
}
a:hover {
text-decoration: underline;
opacity: 1;
}
.button {
background-color: blue;
border: none;
color: white;
padding: 4px 2px;
width: 110px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 12px;
margin: 2px 0px;
}
.button:hover {
background-color: #002db3;
color: white;
}
.buttonhighlight {
background-color: #ff7700;
}
.inputtext {
background-color: white;
border: 1px solid gray;
color: black;
padding: 4px 5px;
width: 95%;
text-align: left;
text-decoration: none;
display: inline-block;
font-size: 12px;
}
.loginform {
margin: auto;
margin-top: 100px;
width: 382px;
border: 2px solid lightblue;
padding: 10px 10px;
font-size: 16px;
align-items: center;
}
.loginerror {
width: 100%;
text-align: center;
color: red;
margin: 5px 10px 10px 10px;
font-size: 16px;
}
.logininput {
border: 1px solid gray;
color: black;
padding: 3px 5px;
margin: 1px 0;
text-align: left;
text-decoration: none;
font-size: 12px;
}
.loginmargin {
margin-top: 10px;
margin-bottom: 8px;
}
header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
color: #fff;
padding: 10px 250px;
background-color: #dddddd;
}
header h2 {
font-family: Arial, Helvetica, sans-serif;
font-size: 32px;
color: blue;
}
header h2 img {
height: 35px;
float: left;
margin-right: 10px;
}
.user {
color: blue;
font-size: 14px;
}
section {
width: 100%;
display: flex;
padding: 10px 250px;
background-color: #f3f3f3;
}
.content {
width: 100%;
}
.buttonrow {
text-align: right;
padding: 0px 0px 5px 0px;
}
.table {
display: table;
width: 100%;
}
.table-row {
display: table-row;
width: 100%;
}
.table-cell {
display: table-cell;
padding: 5px 0px;
}
.bordertop{
border-top: 1px solid gray;
}
.bordertop2{
border-top: 1px solid gray;
}
.twobuttoncell {
width: 225px;
text-align: right;
}
.qrcode {
padding: 5px 5px;
}
.instructions {
font-size: 14px;
}
@media screen and (max-width: 1000px) {
.table, .table-row {
display: block;
}
.table-cell {
display: block;
}
.bordertop2{
border-top: 0;
}
}
footer {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
background-color: #dddddd;
color: blue;
font-size: 12px;
text-align: center;
padding: 3px 5px;
}
@media screen and (max-width: 1200px) {
header {
padding: 10px 100px;
}
section {
padding: 10px 100px;
}
}
@media screen and (max-width: 500px) {
header {
flex-direction: column;
padding: 10px 10px;
}
header h2 {
margin-bottom: 15px;
}
section {
flex-direction: column;
padding: 10px 10px;
}
}

View File

@@ -1 +0,0 @@
TEST

View File

@@ -50,6 +50,8 @@ class WGCfg():
def get_peer(self, peer):
"""Get data of the given WireGuard peer"""
if peer is None:
return None
return self.transform_to_clientdata(peer, self.wc.peers[peer])
def get_peers(self):
@@ -58,11 +60,16 @@ class WGCfg():
def get_peer_byid(self, id):
"""Get data WireGuard peer with the given id"""
peer = next(peer for peer, peerdata in self.get_peers().items() if peerdata['Id'] == id)
try:
peer = next(peer for peer, peerdata in self.get_peers().items() if peerdata['Id'] == id)
except StopIteration:
peer = None
return peer, self.get_peer(peer)
def get_peerconfig(self, peer):
"""Get config for the given WireGuard peer"""
if peer is None:
return None, None
peerdata = self.get_peer(peer)
for item in self.get_interface()['_rawdata']:
if item.startswith('# Endpoint = '):