mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-07-06 17:14:07 +00:00
Many things
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,2 @@
|
||||
config.yaml
|
||||
config.yaml
|
||||
__pycache__/
|
||||
|
3
lib.py
3
lib.py
@ -68,3 +68,6 @@ class Server:
|
||||
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"))
|
||||
|
||||
def create_key(self, key_name):
|
||||
return self.client.create_key(key_name)
|
79
main.py
79
main.py
@ -1,7 +1,8 @@
|
||||
from outline_vpn.outline_vpn import OutlineVPN
|
||||
import yaml
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import random
|
||||
import string
|
||||
|
||||
from flask import Flask, render_template, request, url_for, redirect
|
||||
|
||||
@ -14,16 +15,25 @@ logging.basicConfig(
|
||||
log = logging.getLogger('OutlineFleet')
|
||||
|
||||
SERVERS = list()
|
||||
CLIENTS = list()
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def format_timestamp(ts):
|
||||
return datetime.fromtimestamp(ts//1000).strftime('%Y-%m-%d %H:%M:%S')
|
||||
return datetime.fromtimestamp(ts // 1000).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
def random_string(length=12):
|
||||
letters = string.ascii_letters + string.digits
|
||||
|
||||
return ''.join(random.choice(letters) for i in range(length))
|
||||
|
||||
|
||||
def update_state():
|
||||
global SERVERS
|
||||
global CLIENTS
|
||||
SERVERS = list()
|
||||
CLIENTS = list()
|
||||
config = dict()
|
||||
try:
|
||||
with open("config.yaml", "r") as file:
|
||||
@ -33,12 +43,14 @@ def update_state():
|
||||
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 = config.get('servers', list())
|
||||
for server_id, server_config in servers.items():
|
||||
server = Server(url=server_config["url"], cert=server_config["cert"], comment=server_config["comment"])
|
||||
SERVERS.append(server)
|
||||
log.info("Server found: %s", server.info()["name"])
|
||||
|
||||
CLIENTS = config.get('clients', list())
|
||||
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def index():
|
||||
@ -49,20 +61,42 @@ def index():
|
||||
nt=request.args.get('nt'),
|
||||
nl=request.args.get('nl'),
|
||||
selected_server=request.args.get('selected_server'),
|
||||
add_server=request.args.get('add_server', None),
|
||||
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('/clients', methods=['GET', 'POST'])
|
||||
def clients():
|
||||
if request.method == 'GET':
|
||||
return render_template(
|
||||
'clients.html',
|
||||
SERVERS=SERVERS,
|
||||
CLIENTS=CLIENTS,
|
||||
nt=request.args.get('nt'),
|
||||
nl=request.args.get('nl'),
|
||||
selected_client=request.args.get('selected_client'),
|
||||
add_client=request.args.get('add_client', None),
|
||||
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')))
|
||||
return redirect(
|
||||
url_for('index', nt="Updated Outline VPN Server", selected_server=request.args.get('selected_server')))
|
||||
|
||||
|
||||
@app.route('/add_server', methods=['GET', 'POST'])
|
||||
@app.route('/add_server', methods=['POST'])
|
||||
def add_server():
|
||||
if request.method == 'GET':
|
||||
return render_template('add_server.html')
|
||||
else:
|
||||
if request.method == 'post':
|
||||
with open("config.yaml", "r") as file:
|
||||
config = yaml.safe_load(file) or {}
|
||||
|
||||
@ -86,6 +120,31 @@ def add_server():
|
||||
return redirect(url_for('index', nt="Added Outline VPN Server"))
|
||||
|
||||
|
||||
@app.route('/add_client', methods=['POST'])
|
||||
def add_client():
|
||||
if request.method == 'POST':
|
||||
with open("config.yaml", "r") as file:
|
||||
config = yaml.safe_load(file) or {}
|
||||
|
||||
clients = config.get('clients', dict())
|
||||
user_id = request.form.get('user_id', random_string())
|
||||
|
||||
clients[user_id] = {
|
||||
'name': request.form.get('name'),
|
||||
'comment': request.form.get('comment'),
|
||||
'servers': request.form.getlist('servers')
|
||||
}
|
||||
config["clients"] = clients
|
||||
with open("config.yaml", "w") as file:
|
||||
yaml.safe_dump(config, file)
|
||||
|
||||
for server in SERVERS:
|
||||
if server.data["server_id"] in request.form.getlist('servers'):
|
||||
log.info("%s", server.create_key(request.form.get('name')))
|
||||
update_state()
|
||||
return redirect(url_for('index', nt="User has been added"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
update_state()
|
||||
app.run()
|
||||
|
@ -1,20 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Add new server{% endblock %}
|
||||
{% block content %}
|
||||
<form class="pure-form pure-form-stacked" method="POST">
|
||||
<fieldset>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text" class="pure-u-23-24" name="url" placeholder="Server management URL"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text"class="pure-u-23-24" name="cert" placeholder="Certificate"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text" class="pure-u-23-24" name="comment" placeholder="Comment"/>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="pure-button pure-input-1 pure-button-primary">Add</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,7 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% block title %}Dashboard{% endblock %}
|
||||
|
||||
<title>{% block title %}Dashboard{% endblock %}</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='pure.css') }}">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='layout.css') }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@ -19,7 +20,7 @@
|
||||
<div class="pure-menu custom-restricted-width">
|
||||
<ul class="pure-menu-list">
|
||||
<li class="pure-menu-item"><a href="/" class="pure-menu-link">Servers</a></li>
|
||||
<li class="pure-menu-item"><a href="#" class="pure-menu-link">Clients</a></li>
|
||||
<li class="pure-menu-item"><a href="/clients" class="pure-menu-link">Clients</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
104
templates/clients.html
Normal file
104
templates/clients.html
Normal file
@ -0,0 +1,104 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="list" class="pure-u-1-3" xmlns="http://www.w3.org/1999/html">
|
||||
<div class="server-item pure-g">
|
||||
<h1 class="server-content-title">Clients</h1>
|
||||
</div>
|
||||
{% for client, values in CLIENTS.items() %}
|
||||
<div class="server-item server-item-{% if client == selected_client %}unread{% else %}selected{% endif %} pure-g">
|
||||
<div class="pure-u-3-4" onclick="location.href='/clients?selected_client={{ client }}';">
|
||||
<h5 class="server-name">{{ values["name"] }}</h5>
|
||||
<h4 class="server-info">Allowed {{ values["servers"]|length }} server{% if values["servers"]|length >1 %}s{%endif%}</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div onclick="location.href='/clients?add_client=True';" class="server-item server-add pure-g">
|
||||
<div class="pure-u-1">
|
||||
+
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% if add_client %}
|
||||
<div class="pure-u-1-3">
|
||||
<div class="server-content-header pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<h1 class="server-content-title">Add new client</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="server-content-body">
|
||||
<form action="/add_client" class="pure-form pure-form-stacked" method="POST">
|
||||
<fieldset>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text" class="pure-u-23-24" name="name" required placeholder="Name"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text" class="pure-u-23-24" name="comment" placeholder="Comment"/>
|
||||
</div>
|
||||
<div class="pure-checkbox">
|
||||
{% for server in SERVERS %}
|
||||
<label class="pure-checkbox" for="option{{loop.index0}}">{{server.info()["name"]}}
|
||||
<input type="checkbox" id="option{{loop.index0}}" name="servers" value="{{server.info()['server_id']}}"></label>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="pure-button pure-input-1 pure-button-primary">Add</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if selected_client and not add_client %}
|
||||
{% set client = CLIENTS[selected_client] %}
|
||||
|
||||
<div class="pure-u-1-3">
|
||||
<div class="server-content-header pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<h1 class="server-content-title">{{client['name']}}</h1>
|
||||
<h4 class="server-info">{{ client['comment'] }}</h4>
|
||||
<h4 class="server-info">id {{ selected_client }}</h4>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="server-content-body">
|
||||
<form action="/add_client" class="pure-form pure-form-stacked" method="POST">
|
||||
<fieldset>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text" class="pure-u-23-24" name="name" required value="{{client['name']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text" class="pure-u-23-24" name="comment" value="{{client['comment']}}"/>
|
||||
</div>
|
||||
<input type="hidden" class="pure-u-23-24" name="user_id" value="{{selected_client}}"/>
|
||||
|
||||
<div class="pure-checkbox">
|
||||
{% for server in SERVERS %}
|
||||
<label class="pure-checkbox" for="option{{loop.index0}}">{{server.info()["name"]}}
|
||||
<input
|
||||
{% if server.info()['server_id'] in client['servers'] %}checked{%endif%}
|
||||
type="checkbox" id="option{{loop.index0}}" name="servers" value="{{server.info()['server_id']}}"></label>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="pure-button pure-input-1 pure-button-primary">Add</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div onclick="location.href='/add_server';" class="server-item server-add pure-g">
|
||||
<div onclick="location.href='/?add_server=True';" class="server-item server-add pure-g">
|
||||
<div class="pure-u-1">
|
||||
+
|
||||
</div>
|
||||
@ -28,78 +28,109 @@
|
||||
|
||||
</div>
|
||||
|
||||
{% if SERVERS %}
|
||||
|
||||
{% if server is none %}
|
||||
{% set server = SERVERS[0] %}
|
||||
{% else %}
|
||||
{% set server = SERVERS[selected_server|int] %}
|
||||
{% endif %}
|
||||
<div id="main" class="pure-u-1-3">
|
||||
<div class="server-content">
|
||||
{% if add_server %}
|
||||
<div class="pure-u-1-3">
|
||||
<div class="server-content-header pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<h1 class="server-content-title">{{server.info()["name"]}}</h1>
|
||||
<p class="server-content-subtitle">
|
||||
<span>v.{{server.info()["version"]}} {{server.info()["server_id"]}}</span>
|
||||
</p>
|
||||
<h1 class="server-content-title">Add new server</h1>
|
||||
</div>
|
||||
|
||||
<!-- <div class="server-content-controls pure-u-1-2">-->
|
||||
<!-- <button class="secondary-button pure-button">Reply</button>-->
|
||||
<!-- <button class="secondary-button pure-button">Forward</button>-->
|
||||
<!-- <button class="secondary-button pure-button">Move to</button>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<div class="server-content-body">
|
||||
<h3>Clients: {{ server.info()['keys']|length }}</h3>
|
||||
<form class="pure-form pure-form-stacked" method="POST">
|
||||
<fieldset>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="name">Server Name</br> Note that this will not be reflected on the devices of the users that you invited to connect to it.</label>
|
||||
<input type="text" id="name" class="pure-u-23-24" name="name" value="{{server.info()['name']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="url">Server URL</label>
|
||||
<input type="text" readonly id="url" class="pure-u-23-24" name="url" value="{{server.info()['url']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="comment">Comment</label>
|
||||
<input type="text" id="comment" class="pure-u-23-24" name="comment" value="{{server.info()['comment']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="port_for_new_access_keys">Port For New Access Keys</label>
|
||||
<input type="text" id="port_for_new_access_keys" class="pure-u-23-24" name="port_for_new_access_keys" value="{{server.info()['port_for_new_access_keys']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="hostname_for_access_keys">Hostname For Access Keys</label>
|
||||
<input type="text" id="hostname_for_access_keys" class="pure-u-23-24" name="hostname_for_access_keys" value="{{server.info()['hostname_for_access_keys']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="cert">Server Access Certificate</label>
|
||||
<input type="text" readonly id="cert" class="pure-u-23-24" name="cert" value="{{server.info()['cert']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="created_timestamp_ms">Created</label>
|
||||
<input type="text" readonly id="created_timestamp_ms" class="pure-u-23-24" name="created_timestamp_ms" value="{{format_timestamp(server.info()['created_timestamp_ms']) }}"/>
|
||||
</div>
|
||||
<input type="hidden" readonly id="server_id" class="pure-u-23-24" name="server_id" value="{{server.info()['server_id']}}"/>
|
||||
</div>
|
||||
<p>Share anonymous metrics</p>
|
||||
<label for="metrics_enabled" class="pure-radio">
|
||||
<input type="radio" id="metrics_enabled" name="metrics" value="True" {% if server.info()['metrics_enabled'] == True %}checked{% endif %} /> Enable
|
||||
</label>
|
||||
<label for="metrics_disabled" class="pure-radio">
|
||||
<input type="radio" id="metrics_disabled" name="metrics" value="False" {% if server.info()['metrics_enabled'] == False %}checked{% endif %} /> Disable
|
||||
</label>
|
||||
<form action="/add_server" class="pure-form pure-form-stacked" method="POST">
|
||||
<fieldset>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text" class="pure-u-23-24" name="url" placeholder="Server management URL"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text"class="pure-u-23-24" name="cert" placeholder="Certificate"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<input type="text" class="pure-u-23-24" name="comment" placeholder="Comment"/>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="pure-button pure-input-1 pure-button-primary">Add</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<button type="submit" class="pure-button pure-button-primary">Submit</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
{% if SERVERS|length != 0 and not add_server %}
|
||||
|
||||
{% if selected_server is none %}
|
||||
{% set server = SERVERS[0] %}
|
||||
{% else %}
|
||||
{% set server = SERVERS[selected_server|int] %}
|
||||
{% endif %}
|
||||
<div id="main" class="pure-u-1-3">
|
||||
<div class="server-content">
|
||||
<div class="server-content-header pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<h1 class="server-content-title">{{server.info()["name"]}}</h1>
|
||||
<p class="server-content-subtitle">
|
||||
<span>v.{{server.info()["version"]}} {{server.info()["server_id"]}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- <div class="server-content-controls pure-u-1-2">-->
|
||||
<!-- <button class="secondary-button pure-button">Reply</button>-->
|
||||
<!-- <button class="secondary-button pure-button">Forward</button>-->
|
||||
<!-- <button class="secondary-button pure-button">Move to</button>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<div class="server-content-body">
|
||||
<h3>Clients: {{ server.info()['keys']|length }}</h3>
|
||||
<form class="pure-form pure-form-stacked" method="POST">
|
||||
<fieldset>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="name">Server Name</br> Note that this will not be reflected on the devices of the users that you invited to connect to it.</label>
|
||||
<input type="text" id="name" class="pure-u-23-24" name="name" value="{{server.info()['name']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="comment">Comment</label>
|
||||
<input type="text" id="comment" class="pure-u-23-24" name="comment" value="{{server.info()['comment']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="port_for_new_access_keys">Port For New Access Keys</label>
|
||||
<input type="text" id="port_for_new_access_keys" class="pure-u-23-24" name="port_for_new_access_keys" value="{{server.info()['port_for_new_access_keys']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="hostname_for_access_keys">Hostname For Access Keys</label>
|
||||
<input type="text" id="hostname_for_access_keys" class="pure-u-23-24" name="hostname_for_access_keys" value="{{server.info()['hostname_for_access_keys']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="url">Server URL</label>
|
||||
<input type="text" readonly id="url" class="pure-u-23-24" name="url" value="{{server.info()['url']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="cert">Server Access Certificate</label>
|
||||
<input type="text" readonly id="cert" class="pure-u-23-24" name="cert" value="{{server.info()['cert']}}"/>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<label for="created_timestamp_ms">Created</label>
|
||||
<input type="text" readonly id="created_timestamp_ms" class="pure-u-23-24" name="created_timestamp_ms" value="{{format_timestamp(server.info()['created_timestamp_ms']) }}"/>
|
||||
</div>
|
||||
<input type="hidden" readonly id="server_id" class="pure-u-23-24" name="server_id" value="{{server.info()['server_id']}}"/>
|
||||
</div>
|
||||
<p>Share anonymous metrics</p>
|
||||
<label for="metrics_enabled" class="pure-radio">
|
||||
<input type="radio" id="metrics_enabled" name="metrics" value="True" {% if server.info()['metrics_enabled'] == True %}checked{% endif %} /> Enable
|
||||
</label>
|
||||
<label for="metrics_disabled" class="pure-radio">
|
||||
<input type="radio" id="metrics_disabled" name="metrics" value="False" {% if server.info()['metrics_enabled'] == False %}checked{% endif %} /> Disable
|
||||
</label>
|
||||
|
||||
<button type="submit" class="pure-button pure-button-primary">Submit</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
Reference in New Issue
Block a user