From 39e480b65597c6f872d87ae2ec226fb74b95d9a1 Mon Sep 17 00:00:00 2001 From: Alexandr Bogomyakov Date: Mon, 25 Sep 2023 01:19:50 +0300 Subject: [PATCH] Init --- .gitignore | 1 + .idea/.gitignore | 3 + .idea/OutlineFleet.iml | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + lib.py | 70 ++++ main.py | 91 +++++ requirements.txt | 3 + static/layout.css | 355 ++++++++++++++++++ static/pure.css | 11 + templates/add_server.html | 20 + templates/base.html | 90 +++++ templates/index.html | 105 ++++++ 15 files changed, 786 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/OutlineFleet.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 lib.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 static/layout.css create mode 100644 static/pure.css create mode 100644 templates/add_server.html create mode 100644 templates/base.html create mode 100644 templates/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a539470 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.yaml \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/OutlineFleet.iml b/.idea/OutlineFleet.iml new file mode 100644 index 0000000..74d515a --- /dev/null +++ b/.idea/OutlineFleet.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3cca3bf --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..67f92ec --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/lib.py b/lib.py new file mode 100644 index 0000000..d2c6f03 --- /dev/null +++ b/lib.py @@ -0,0 +1,70 @@ +import logging +from typing import TypedDict, List +from outline_vpn.outline_vpn import OutlineKey, OutlineVPN +import yaml + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%d-%m-%Y %H:%M:%S') +log = logging.getLogger('OutlineFleet.lib') + + +class ServerDict(TypedDict): + name: str + url: str + cert: str + comment: str + server_id: str + metrics_enabled: str + created_timestamp_ms: int + version: str + port_for_new_access_keys: int + hostname_for_access_keys: str + keys: List[OutlineKey] + + +class Server: + def __init__(self, + url: str, + cert: str, + comment: str, + ): + self.client = OutlineVPN(api_url=url, cert_sha256=cert) + self.data: ServerDict = { + 'name': self.client.get_server_information()["name"], + 'url': url, + 'cert': cert, + 'comment': comment, + 'server_id': self.client.get_server_information()["serverId"], + 'metrics_enabled': self.client.get_server_information()["metricsEnabled"], + 'created_timestamp_ms': self.client.get_server_information()["createdTimestampMs"], + 'version': self.client.get_server_information()["version"], + 'port_for_new_access_keys': self.client.get_server_information()["portForNewAccessKeys"], + 'hostname_for_access_keys': self.client.get_server_information()["hostnameForAccessKeys"], + 'keys': self.client.get_keys() + } + + def info(self) -> ServerDict: + return self.data + + def apply_config(self, config): + if config.get("name"): + self.client.set_server_name(config.get("name")) + log.info("Changed %s name to '%s'", self.data["server_id"], config.get("name")) + if config.get("metrics"): + self.client.set_metrics_status(True if config.get("metrics") == 'True' else False) + log.info("Changed %s metrics status to '%s'", self.data["server_id"], config.get("metrics")) + if config.get("port_for_new_access_keys"): + self.client.set_port_new_for_access_keys(int(config.get("port_for_new_access_keys"))) + log.info("Changed %s port_for_new_access_keys to '%s'", self.data["server_id"], config.get("port_for_new_access_keys")) + if config.get("hostname_for_access_keys"): + self.client.set_hostname(config.get("hostname_for_access_keys")) + log.info("Changed %s hostname_for_access_keys to '%s'", self.data["server_id"], config.get("hostname_for_access_keys")) + if config.get("comment"): + with open("config.yaml", "r") as file: + config_file = yaml.safe_load(file) or {} + config_file["servers"][self.data['server_id']]['comment'] = config.get("comment") + with open("config.yaml", "w") as file: + yaml.safe_dump(config_file, file) + log.info("Changed %s comment to '%s'", self.data["server_id"], config.get("comment")) diff --git a/main.py b/main.py new file mode 100644 index 0000000..38aac86 --- /dev/null +++ b/main.py @@ -0,0 +1,91 @@ +from outline_vpn.outline_vpn import OutlineVPN +import yaml +import logging +from datetime import datetime + +from flask import Flask, render_template, request, url_for, redirect + +from lib import Server + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%d-%m-%Y %H:%M:%S') +log = logging.getLogger('OutlineFleet') + +SERVERS = list() +app = Flask(__name__) + + +def format_timestamp(ts): + return datetime.fromtimestamp(ts//1000).strftime('%Y-%m-%d %H:%M:%S') + + +def update_state(): + global SERVERS + SERVERS = list() + config = dict() + try: + with open("config.yaml", "r") as file: + config = yaml.safe_load(file) + except: + with open("config.yaml", "w"): + pass + + if config: + servers = config.get('servers', None) + for server_id, config in servers.items(): + server = Server(url=config["url"], cert=config["cert"], comment=config["comment"]) + SERVERS.append(server) + log.info("Server found: %s", server.info()["name"]) + + +@app.route('/', methods=['GET', 'POST']) +def index(): + if request.method == 'GET': + return render_template( + 'index.html', + SERVERS=SERVERS, + nt=request.args.get('nt'), + nl=request.args.get('nl'), + selected_server=request.args.get('selected_server'), + format_timestamp=format_timestamp) + else: + server = request.form['server_id'] + server = next((item for item in SERVERS if item.info()["server_id"] == server), None) + server.apply_config(request.form) + update_state() + return redirect(url_for('index', nt="Updated Outline VPN Server", selected_server=request.args.get('selected_server'))) + + +@app.route('/add_server', methods=['GET', 'POST']) +def add_server(): + if request.method == 'GET': + return render_template('add_server.html') + else: + with open("config.yaml", "r") as file: + config = yaml.safe_load(file) or {} + + servers = config.get('servers', dict()) + + try: + new_server = Server(url=request.form['url'], cert=request.form['cert'], comment=request.form['comment']) + except: + return redirect(url_for('index', nt="Couldn't access Outline VPN Server", nl="error")) + + servers[new_server.data["server_id"]] = { + 'name': new_server.data["name"], + 'url': new_server.data["url"], + 'comment': new_server.data["comment"], + 'cert': request.form['cert'] + } + config["servers"] = servers + with open("config.yaml", "w") as file: + yaml.safe_dump(config, file) + update_state() + return redirect(url_for('index', nt="Added Outline VPN Server")) + + +if __name__ == '__main__': + update_state() + app.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f9485d1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +outline-vpn-api +PyYAML +Flask \ No newline at end of file diff --git a/static/layout.css b/static/layout.css new file mode 100644 index 0000000..bca6992 --- /dev/null +++ b/static/layout.css @@ -0,0 +1,355 @@ +/* + * -- BASE STYLES -- + * Most of these are inherited from Base, but I want to change a few. + */ +body { + color: #333; +} + + + +a { + text-decoration: none; + color: #1b98f8; +} + + +/* + * -- HELPER STYLES -- + * Over-riding some of the .pure-button styles to make my buttons look unique + */ +.primary-button, +.secondary-button { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + border-radius: 20px; +} +.primary-button { + color: #fff; + background: #1b98f8; + margin: 1em 0; +} +.secondary-button { + background: #fff; + border: 1px solid #ddd; + color: #666; + padding: 0.5em 2em; + font-size: 80%; +} + +/* + * -- LAYOUT STYLES -- + * This layout consists of three main elements, `#nav` (navigation bar), `#list` (server list), and `#main` (server content). All 3 elements are within `#layout` + */ +#layout, #nav, #list, #main { + margin: 0; + padding: 0; +} + +/* Make the navigation 100% width on phones */ +#nav { + width: 100%; + height: 40px; + position: relative; + background: rgb(37, 42, 58); + text-align: center; +} +/* Show the "Menu" button on phones */ +#nav .nav-menu-button { + display: block; + top: 0.5em; + right: 0.5em; + position: absolute; +} + +/* When "Menu" is clicked, the navbar should be 80% height */ +#nav.active { + height: 80%; +} +/* Don't show the navigation items... */ +.nav-inner { + display: none; +} + +/* ...until the "Menu" button is clicked */ +#nav.active .nav-inner { + display: block; + padding: 2em 0; +} + + +/* + * -- NAV BAR STYLES -- + * Styling the default .pure-menu to look a little more unique. + */ +#nav .pure-menu { + background: transparent; + border: none; + text-align: left; +} + #nav .pure-menu-link:hover, + #nav .pure-menu-link:focus { + background: rgb(55, 60, 90); + } + #nav .pure-menu-link { + color: #fff; + margin-left: 0.5em; + } + #nav .pure-menu-heading { + border-bottom: none; + font-size:110%; + color: rgb(75, 113, 151); + } + + +/* + * -- server STYLES -- + * Styles relevant to the server messages, labels, counts, and more. + */ +.server-count { + color: rgb(75, 113, 151); +} + +.server-label-personal, +.server-label-work, +.server-label-travel { + width: 15px; + height: 15px; + display: inline-block; + margin-right: 0.5em; + border-radius: 3px; +} +.server-label-personal { + background: #ffc94c; +} +.server-label-work { + background: #41ccb4; +} +.server-label-travel { + background: #40c365; +} + + +/* server Item Styles */ +.server-item { + padding: 0.9em 1em; + border-bottom: 1px solid #ddd; + border-left: 6px solid transparent; +} + .server-name { + text-transform: uppercase; + } + .server-name, + .server-info { + margin: 0; + font-size: 100%; + } + .server-info { + color: #999; + font-size: 80%; + } + .server-comment { + font-size: 90%; + margin: 0.4em 0; + } + .server-add { + cursor: pointer; + text-align: center; + font-size: 150%; + color: #999; + } + .server-add:hover { + color: black; + } + +.server-item-selected { + background: #eee; +} +.server-item-unread { + border-left: 6px solid #1b98f8; +} + +/* server Content Styles */ +.server-content-header, .server-content-body, .server-content-footer { + padding: 1em 2em; +} + .server-content-header { + border-bottom: 1px solid #ddd; + } + + .server-content-title { + margin: 0.5em 0 0; + } + .server-content-subtitle { + font-size: 1em; + margin: 0; + font-weight: normal; + } + .server-content-subtitle span { + color: #999; + } + .server-content-controls { + margin-top: 2em; + text-align: right; + } + .server-content-controls .secondary-button { + margin-bottom: 0.3em; + } + + .server-avatar { + width: 40px; + height: 40px; + } + + +/* + * -- TABLET (AND UP) MEDIA QUERIES -- + * On tablets and other medium-sized devices, we want to customize some + * of the mobile styles. + */ +@media (min-width: 40em) { + + /* Move the layout over so we can fit the nav + list in on the left */ + #layout { + padding-left:500px; /* "left col (nav + list)" width */ + position: relative; + } + + /* These are position:fixed; elements that will be in the left 500px of the screen */ + #nav, #list { + position: fixed; + top: 0; + bottom: 0; + overflow: auto; + } + #nav { + margin-left:-500px; /* "left col (nav + list)" width */ + width:150px; + height: 100%; + } + + /* Show the menu items on the larger screen */ + .nav-inner { + display: block; + padding: 2em 0; + } + + /* Hide the "Menu" button on larger screens */ + #nav .nav-menu-button { + display: none; + } + + #list { + margin-left: -350px; + width: 100%; + height: 33%; + border-bottom: 1px solid #ddd; + } + + #main { + position: fixed; + top: 33%; + right: 0; + bottom: 0; + left: 150px; + overflow: auto; + width: auto; /* so that it's not 100% */ + } + +} + +/* + * -- DESKTOP (AND UP) MEDIA QUERIES -- + * On desktops and other large-sized devices, we want to customize some + * of the mobile styles. + */ +@media (min-width: 60em) { + + /* This will take up the entire height, and be a little thinner */ + #list { + margin-left: -350px; + width:350px; + height: 100%; + border-right: 1px solid #ddd; + } + + /* This will now take up it's own column, so don't need position: fixed; */ + #main { + position: static; + margin: 0; + padding: 0; + } +} + + +.alert { + position: absolute; + top: 1em; + right: 1em; + width: auto; + height: auto; + padding: 10px; + margin: 10px; + line-height: 1.8; + border-radius: 5px; + cursor: hand; + cursor: pointer; + font-family: sans-serif; + font-weight: 400; +} + +.alertCheckbox { + display: none; +} + +:checked + .alert { + display: none; +} + +.alertText { + display: table; + margin: 0 auto; + text-align: center; + font-size: 150%; +} + +.alertClose { + float: right; + padding-top: 0px; + font-size: 120%; +} + +.clear { + clear: both; +} + +.info { + background-color: #EEE; + border: 1px solid #DDD; + color: #999; +} + +.success { + background-color: #EFE; + border: 1px solid #DED; + color: #9A9; +} + +.notice { + background-color: #EFF; + border: 1px solid #DEE; + color: #9AA; +} + +.warning { + background-color: #FDF7DF; + border: 1px solid #FEEC6F; + color: #C9971C; +} + +.error { + background-color: #FEE; + border: 1px solid #EDD; + color: #A66; +} \ No newline at end of file diff --git a/static/pure.css b/static/pure.css new file mode 100644 index 0000000..acdc431 --- /dev/null +++ b/static/pure.css @@ -0,0 +1,11 @@ +/*! +Pure v3.0.0 +Copyright 2013 Yahoo! +Licensed under the BSD License. +https://github.com/pure-css/pure/blob/master/LICENSE +*/ +/*! +normalize.css v | MIT License | https://necolas.github.io/normalize.css/ +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{display:flex;flex-flow:row wrap;align-content:flex-start}.pure-u{display:inline-block;vertical-align:top}.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}.pure-button{display:inline-block;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;user-select:none;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-0.43em}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:rgba(0,0,0,.8);border:none transparent;background-color:#e6e6e6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;opacity:.4;cursor:not-allowed;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{margin:0;border-radius:0;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none}.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=color]:focus,.pure-form input[type=date]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=email]:focus,.pure-form input[type=month]:focus,.pure-form input[type=number]:focus,.pure-form input[type=password]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=text]:focus,.pure-form input[type=time]:focus,.pure-form input[type=url]:focus,.pure-form input[type=week]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129fea}.pure-form input:not([type]):focus{outline:0;border-color:#129fea}.pure-form input[type=checkbox]:focus,.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=color][disabled],.pure-form input[type=date][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=email][disabled],.pure-form input[type=month][disabled],.pure-form input[type=number][disabled],.pure-form input[type=password][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=text][disabled],.pure-form input[type=time][disabled],.pure-form input[type=url][disabled],.pure-form input[type=week][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=color],.pure-form-stacked input[type=date],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=email],.pure-form-stacked input[type=file],.pure-form-stacked input[type=month],.pure-form-stacked input[type=number],.pure-form-stacked input[type=password],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=text],.pure-form-stacked input[type=time],.pure-form-stacked input[type=url],.pure-form-stacked input[type=week],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=color],.pure-group input[type=date],.pure-group input[type=datetime-local],.pure-group input[type=datetime],.pure-group input[type=email],.pure-group input[type=month],.pure-group input[type=number],.pure-group input[type=password],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=text],.pure-group input[type=time],.pure-group input[type=url],.pure-group input[type=week]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0 0}.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;padding:.5em 0}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent;cursor:default}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected>.pure-menu-link,.pure-menu-selected>.pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} \ No newline at end of file diff --git a/templates/add_server.html b/templates/add_server.html new file mode 100644 index 0000000..df07c08 --- /dev/null +++ b/templates/add_server.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% block title %}Add new server{% endblock %} +{% block content %} +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..06c9f88 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,90 @@ + + + + {% block title %}Dashboard{% endblock %} + + + + + + + +
+ + {% block content %}{% endblock %} +
+ + + +{% if nt %} + +{% endif %} + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..f5e9770 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} + +{% block content %} + +
+
+

Servers

+
+ {% for server in SERVERS %} +
+
+
{{ server.info()["name"] }}
+

{{ '/'.join(server.info()["url"].split('/')[0:-1]) }}

+

Port {{ server.info()["port_for_new_access_keys"] }}

+

Hostname {{ server.info()["hostname_for_access_keys"] }}

+

v.{{ server.info()["version"] }}

+

+ {{ server.info()["comment"] }} +

+
+
+ {% endfor %} +
+
+ + +
+
+ +
+ +{% if SERVERS %} + +{% if server is none %} + {% set server = SERVERS[0] %} +{% else %} + {% set server = SERVERS[selected_server|int] %} +{% endif %} +
+
+
+
+

{{server.info()["name"]}}

+

+ v.{{server.info()["version"]}} {{server.info()["server_id"]}} +

+
+ + + + + + +
+ +
+

Clients: {{ server.info()['keys']|length }}

+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+

Share anonymous metrics

+ + + + +
+
+
+
+
+{% endif %} +{% endblock %} \ No newline at end of file