mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-07-07 01:24:06 +00:00
0
.github/workflows/main.yml
vendored
Normal file → Executable file
0
.github/workflows/main.yml
vendored
Normal file → Executable file
3
.gitignore
vendored
Normal file → Executable file
3
.gitignore
vendored
Normal file → Executable file
@ -2,7 +2,8 @@ config.yaml
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
sync.log
|
sync.log
|
||||||
main.py
|
main.py
|
||||||
.vscode/launch.json
|
.idea/*
|
||||||
|
.vscode/*
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*.swn
|
*.swn
|
||||||
|
3
.idea/.gitignore
generated
vendored
3
.idea/.gitignore
generated
vendored
@ -1,3 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
13
.idea/OutlineFleet.iml
generated
13
.idea/OutlineFleet.iml
generated
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="PYTHON_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
<component name="PackageRequirementsSettings">
|
|
||||||
<option name="versionSpecifier" value="Strong equality (==x.y.z)" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
10
.idea/misc.xml
generated
10
.idea/misc.xml
generated
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Black">
|
|
||||||
<option name="sdkName" value="Python 3.10 (outfleet)" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (outfleet)" project-jdk-type="Python SDK" />
|
|
||||||
<component name="PyCharmProfessionalAdvertiser">
|
|
||||||
<option name="shown" value="true" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/OutlineFleet.iml" filepath="$PROJECT_DIR$/.idea/OutlineFleet.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"tht13.python",
|
|
||||||
"ms-python.python"
|
|
||||||
]
|
|
||||||
}
|
|
0
Dockerfile
Normal file → Executable file
0
Dockerfile
Normal file → Executable file
0
buildx.yaml
Normal file → Executable file
0
buildx.yaml
Normal file → Executable file
0
img/servers.png
Normal file → Executable file
0
img/servers.png
Normal file → Executable file
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
77
k8s.py
Executable file
77
k8s.py
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
import logging
|
||||||
|
from kubernetes import client, config
|
||||||
|
from kubernetes.client.rest import ApiException
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
|
datefmt="%d-%m-%Y %H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
|
log = logging.getLogger("OutFleet.k8s")
|
||||||
|
file_handler = logging.FileHandler("sync.log")
|
||||||
|
file_handler.setLevel(logging.DEBUG)
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
log.addHandler(file_handler)
|
||||||
|
|
||||||
|
def write_config(config):
|
||||||
|
config_map = client.V1ConfigMap(
|
||||||
|
api_version="v1",
|
||||||
|
kind="ConfigMap",
|
||||||
|
metadata=client.V1ObjectMeta(
|
||||||
|
name=f"config-outfleet",
|
||||||
|
labels={
|
||||||
|
"app": "outfleet",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
data={"config.yaml": yaml.dump(config)}
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
api_response = v1.create_namespaced_config_map(
|
||||||
|
namespace=NAMESPACE,
|
||||||
|
body=config_map,
|
||||||
|
)
|
||||||
|
except ApiException as e:
|
||||||
|
api_response = v1.patch_namespaced_config_map(
|
||||||
|
name="config-outfleet",
|
||||||
|
namespace=NAMESPACE,
|
||||||
|
body=config_map,
|
||||||
|
)
|
||||||
|
|
||||||
|
config.load_incluster_config()
|
||||||
|
|
||||||
|
v1 = client.CoreV1Api()
|
||||||
|
|
||||||
|
NAMESPACE = False
|
||||||
|
SERVERS = list()
|
||||||
|
CONFIG = None
|
||||||
|
|
||||||
|
log.info("Checking for Kubernetes environment")
|
||||||
|
try:
|
||||||
|
with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace") as f:
|
||||||
|
NAMESPACE = f.read().strip()
|
||||||
|
log.info(f"Found Kubernetes environment. Namespace {NAMESPACE}")
|
||||||
|
except IOError:
|
||||||
|
log.info("Kubernetes environment not detected")
|
||||||
|
pass
|
||||||
|
|
||||||
|
# config = v1.list_namespaced_config_map(NAMESPACE, label_selector="app=outfleet").items["data"]["config.yaml"]
|
||||||
|
try:
|
||||||
|
CONFIG = yaml.safe_load(v1.read_namespaced_config_map(name="config-outfleet", namespace=NAMESPACE).data['config.yaml'])
|
||||||
|
log.info(f"ConfigMap config.yaml loaded from Kubernetes API. Servers: {len(CONFIG['servers'])}, Clients: {len(CONFIG['clients'])}")
|
||||||
|
except ApiException as e:
|
||||||
|
log.warning(f"ConfigMap not found. Fisrt run?")
|
||||||
|
|
||||||
|
#servers = v1.list_namespaced_secret(NAMESPACE, label_selector="app=shadowbox")
|
||||||
|
|
||||||
|
if not CONFIG:
|
||||||
|
log.info(f"Creating new ConfigMap [config-outfleet]")
|
||||||
|
write_config({"clients": [], "servers": [], "ui_hostname": "accessible-address.com"})
|
||||||
|
CONFIG = yaml.safe_load(v1.read_namespaced_config_map(name="config-outfleet", namespace=NAMESPACE).data['config.yaml'])
|
||||||
|
|
45
lib.py
Normal file → Executable file
45
lib.py
Normal file → Executable file
@ -1,7 +1,10 @@
|
|||||||
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
from typing import TypedDict, List
|
from typing import TypedDict, List
|
||||||
from outline_vpn.outline_vpn import OutlineKey, OutlineVPN
|
from outline_vpn.outline_vpn import OutlineKey, OutlineVPN
|
||||||
import yaml
|
import yaml
|
||||||
|
import k8s
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
@ -9,6 +12,42 @@ logging.basicConfig(
|
|||||||
datefmt="%d-%m-%Y %H:%M:%S",
|
datefmt="%d-%m-%Y %H:%M:%S",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
log = logging.getLogger(f'OutFleet.lib')
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--config",
|
||||||
|
default="/usr/local/etc/outfleet/config.yaml",
|
||||||
|
help="Config file location",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
def get_config():
|
||||||
|
if not k8s.NAMESPACE:
|
||||||
|
try:
|
||||||
|
with open(args.config, "r") as file:
|
||||||
|
config = yaml.safe_load(file)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
with open(args.config, "w"):
|
||||||
|
pass
|
||||||
|
except Exception as exp:
|
||||||
|
log.error(f"Couldn't create config. {exp}")
|
||||||
|
return None
|
||||||
|
return config
|
||||||
|
else:
|
||||||
|
return k8s.CONFIG
|
||||||
|
|
||||||
|
def write_config(config):
|
||||||
|
if not k8s.NAMESPACE:
|
||||||
|
try:
|
||||||
|
with open(args.config, "w") as file:
|
||||||
|
yaml.safe_dump(config, file)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Couldn't write Outfleet config: {e}")
|
||||||
|
else:
|
||||||
|
k8s.write_config(config)
|
||||||
|
|
||||||
|
|
||||||
class ServerDict(TypedDict):
|
class ServerDict(TypedDict):
|
||||||
server_id: str
|
server_id: str
|
||||||
@ -114,13 +153,11 @@ class Server:
|
|||||||
config.get("hostname_for_access_keys"),
|
config.get("hostname_for_access_keys"),
|
||||||
)
|
)
|
||||||
if config.get("comment"):
|
if config.get("comment"):
|
||||||
with open(CFG_PATH, "r") as file:
|
config_file = get_config()
|
||||||
config_file = yaml.safe_load(file) or {}
|
|
||||||
config_file["servers"][self.data["local_server_id"]]["comment"] = config.get(
|
config_file["servers"][self.data["local_server_id"]]["comment"] = config.get(
|
||||||
"comment"
|
"comment"
|
||||||
)
|
)
|
||||||
with open(CFG_PATH, "w") as file:
|
write_config(config_file)
|
||||||
yaml.safe_dump(config_file, file)
|
|
||||||
self.log.info(
|
self.log.info(
|
||||||
"Changed %s comment to '%s'",
|
"Changed %s comment to '%s'",
|
||||||
self.data["local_server_id"],
|
self.data["local_server_id"],
|
||||||
|
90
main.py
Normal file → Executable file
90
main.py
Normal file → Executable file
@ -6,9 +6,12 @@ import string
|
|||||||
import argparse
|
import argparse
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
import k8s
|
||||||
from flask import Flask, render_template, request, url_for, redirect
|
from flask import Flask, render_template, request, url_for, redirect
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from lib import Server
|
from lib import Server, write_config, get_config, args
|
||||||
|
|
||||||
|
|
||||||
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
||||||
|
|
||||||
@ -27,17 +30,11 @@ formatter = logging.Formatter(
|
|||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(formatter)
|
||||||
log.addHandler(file_handler)
|
log.addHandler(file_handler)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument(
|
|
||||||
"-c",
|
|
||||||
"--config",
|
|
||||||
default="/usr/local/etc/outfleet/config.yaml",
|
|
||||||
help="Config file location",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
CFG_PATH = args.config
|
|
||||||
|
|
||||||
|
CFG_PATH = args.config
|
||||||
|
NAMESPACE = k8s.NAMESPACE
|
||||||
SERVERS = list()
|
SERVERS = list()
|
||||||
|
BROKEN_SERVERS = list()
|
||||||
CLIENTS = dict()
|
CLIENTS = dict()
|
||||||
VERSION = '3'
|
VERSION = '3'
|
||||||
HOSTNAME = ""
|
HOSTNAME = ""
|
||||||
@ -55,22 +52,18 @@ def random_string(length=64):
|
|||||||
return "".join(random.choice(letters) for i in range(length))
|
return "".join(random.choice(letters) for i in range(length))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def update_state():
|
def update_state():
|
||||||
global SERVERS
|
global SERVERS
|
||||||
global CLIENTS
|
global CLIENTS
|
||||||
|
global BROKEN_SERVERS
|
||||||
global HOSTNAME
|
global HOSTNAME
|
||||||
|
|
||||||
SERVERS = list()
|
SERVERS = list()
|
||||||
|
BROKEN_SERVERS = list()
|
||||||
CLIENTS = dict()
|
CLIENTS = dict()
|
||||||
config = dict()
|
config = get_config()
|
||||||
try:
|
|
||||||
with open(CFG_PATH, "r") as file:
|
|
||||||
config = yaml.safe_load(file)
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
with open(CFG_PATH, "w"):
|
|
||||||
pass
|
|
||||||
except Exception as exp:
|
|
||||||
log.error(f"Couldn't create config. {exp}")
|
|
||||||
|
|
||||||
if config:
|
if config:
|
||||||
HOSTNAME = config.get("ui_hostname", "my-own-SSL-ENABLED-domain.com")
|
HOSTNAME = config.get("ui_hostname", "my-own-SSL-ENABLED-domain.com")
|
||||||
@ -90,6 +83,11 @@ def update_state():
|
|||||||
local_server_id,
|
local_server_id,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
BROKEN_SERVERS.append({
|
||||||
|
"config": server_config,
|
||||||
|
"error": e,
|
||||||
|
"id": local_server_id
|
||||||
|
})
|
||||||
log.warning("Can't access server: %s - %s", server_config["url"], e)
|
log.warning("Can't access server: %s - %s", server_config["url"], e)
|
||||||
|
|
||||||
CLIENTS = config.get("clients", dict())
|
CLIENTS = config.get("clients", dict())
|
||||||
@ -98,13 +96,16 @@ def update_state():
|
|||||||
@app.route("/", methods=["GET", "POST"])
|
@app.route("/", methods=["GET", "POST"])
|
||||||
def index():
|
def index():
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
|
#if request.args.get("broken") == True:
|
||||||
return render_template(
|
return render_template(
|
||||||
"index.html",
|
"index.html",
|
||||||
SERVERS=SERVERS,
|
SERVERS=SERVERS,
|
||||||
VERSION=VERSION,
|
VERSION=VERSION,
|
||||||
|
BROKEN_SERVERS=BROKEN_SERVERS,
|
||||||
nt=request.args.get("nt"),
|
nt=request.args.get("nt"),
|
||||||
nl=request.args.get("nl"),
|
nl=request.args.get("nl"),
|
||||||
selected_server=request.args.get("selected_server"),
|
selected_server=request.args.get("selected_server"),
|
||||||
|
broken=request.args.get("broken", False),
|
||||||
add_server=request.args.get("add_server", None),
|
add_server=request.args.get("add_server", None),
|
||||||
format_timestamp=format_timestamp,
|
format_timestamp=format_timestamp,
|
||||||
)
|
)
|
||||||
@ -128,13 +129,6 @@ def index():
|
|||||||
|
|
||||||
@app.route("/clients", methods=["GET", "POST"])
|
@app.route("/clients", methods=["GET", "POST"])
|
||||||
def clients():
|
def clients():
|
||||||
# {% for server in SERVERS %}
|
|
||||||
# {% for key in server.data["keys"] %}
|
|
||||||
# {% if key.name == client['name'] %}
|
|
||||||
# ssconf://{{ dynamic_hostname }}/dynamic/{{server.info()['name']}}/{{selected_client}}#{{server.info()['comment']}}
|
|
||||||
# {% endif %}
|
|
||||||
# {% endfor %}
|
|
||||||
# {% endfor %}
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
return render_template(
|
return render_template(
|
||||||
"clients.html",
|
"clients.html",
|
||||||
@ -148,22 +142,13 @@ def clients():
|
|||||||
format_timestamp=format_timestamp,
|
format_timestamp=format_timestamp,
|
||||||
dynamic_hostname=HOSTNAME,
|
dynamic_hostname=HOSTNAME,
|
||||||
)
|
)
|
||||||
# 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=["POST"])
|
@app.route("/add_server", methods=["POST"])
|
||||||
def add_server():
|
def add_server():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
try:
|
try:
|
||||||
with open(CFG_PATH, "r") as file:
|
config = get_config()
|
||||||
config = yaml.safe_load(file) or {}
|
|
||||||
|
|
||||||
servers = config.get("servers", dict())
|
servers = config.get("servers", dict())
|
||||||
local_server_id = str(uuid.uuid4())
|
local_server_id = str(uuid.uuid4())
|
||||||
|
|
||||||
@ -181,16 +166,7 @@ def add_server():
|
|||||||
"cert": request.form["cert"],
|
"cert": request.form["cert"],
|
||||||
}
|
}
|
||||||
config["servers"] = servers
|
config["servers"] = servers
|
||||||
try:
|
write_config(config)
|
||||||
with open(CFG_PATH, "w") as file:
|
|
||||||
yaml.safe_dump(config, file)
|
|
||||||
except Exception as e:
|
|
||||||
return redirect(
|
|
||||||
url_for(
|
|
||||||
"index", nt=f"Couldn't write Outfleet config: {e}", nl="error"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
log.info("Added server: %s", new_server.data["name"])
|
log.info("Added server: %s", new_server.data["name"])
|
||||||
update_state()
|
update_state()
|
||||||
return redirect(url_for("index", nt="Added Outline VPN Server"))
|
return redirect(url_for("index", nt="Added Outline VPN Server"))
|
||||||
@ -204,8 +180,7 @@ def add_server():
|
|||||||
@app.route("/del_server", methods=["POST"])
|
@app.route("/del_server", methods=["POST"])
|
||||||
def del_server():
|
def del_server():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
with open(CFG_PATH, "r") as file:
|
config = get_config()
|
||||||
config = yaml.safe_load(file) or {}
|
|
||||||
|
|
||||||
local_server_id = request.form.get("local_server_id")
|
local_server_id = request.form.get("local_server_id")
|
||||||
server_name = None
|
server_name = None
|
||||||
@ -218,9 +193,7 @@ def del_server():
|
|||||||
client_config["servers"].remove(local_server_id)
|
client_config["servers"].remove(local_server_id)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
pass
|
pass
|
||||||
|
write_config(config)
|
||||||
with open(CFG_PATH, "w") as file:
|
|
||||||
yaml.safe_dump(config, file)
|
|
||||||
log.info("Deleting server %s [%s]", server_name, request.form.get("local_server_id"))
|
log.info("Deleting server %s [%s]", server_name, request.form.get("local_server_id"))
|
||||||
update_state()
|
update_state()
|
||||||
return redirect(url_for("index", nt=f"Server {server_name} has been deleted"))
|
return redirect(url_for("index", nt=f"Server {server_name} has been deleted"))
|
||||||
@ -229,8 +202,7 @@ def del_server():
|
|||||||
@app.route("/add_client", methods=["POST"])
|
@app.route("/add_client", methods=["POST"])
|
||||||
def add_client():
|
def add_client():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
with open(CFG_PATH, "r") as file:
|
config = get_config()
|
||||||
config = yaml.safe_load(file) or {}
|
|
||||||
|
|
||||||
clients = config.get("clients", dict())
|
clients = config.get("clients", dict())
|
||||||
user_id = request.form.get("user_id", random_string())
|
user_id = request.form.get("user_id", random_string())
|
||||||
@ -241,8 +213,7 @@ def add_client():
|
|||||||
"servers": request.form.getlist("servers"),
|
"servers": request.form.getlist("servers"),
|
||||||
}
|
}
|
||||||
config["clients"] = clients
|
config["clients"] = clients
|
||||||
with open(CFG_PATH, "w") as file:
|
write_config(config)
|
||||||
yaml.safe_dump(config, file)
|
|
||||||
log.info("Client %s updated", request.form.get("name"))
|
log.info("Client %s updated", request.form.get("name"))
|
||||||
|
|
||||||
for server in SERVERS:
|
for server in SERVERS:
|
||||||
@ -304,9 +275,7 @@ def add_client():
|
|||||||
@app.route("/del_client", methods=["POST"])
|
@app.route("/del_client", methods=["POST"])
|
||||||
def del_client():
|
def del_client():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
with open(CFG_PATH, "r") as file:
|
config = get_config()
|
||||||
config = yaml.safe_load(file) or {}
|
|
||||||
|
|
||||||
clients = config.get("clients", dict())
|
clients = config.get("clients", dict())
|
||||||
user_id = request.form.get("user_id")
|
user_id = request.form.get("user_id")
|
||||||
if user_id in clients:
|
if user_id in clients:
|
||||||
@ -323,8 +292,7 @@ def del_client():
|
|||||||
server.delete_key(client.key_id)
|
server.delete_key(client.key_id)
|
||||||
|
|
||||||
config["clients"].pop(user_id)
|
config["clients"].pop(user_id)
|
||||||
with open(CFG_PATH, "w") as file:
|
write_config(config)
|
||||||
yaml.safe_dump(config, file)
|
|
||||||
log.info("Deleting client %s", request.form.get("name"))
|
log.info("Deleting client %s", request.form.get("name"))
|
||||||
update_state()
|
update_state()
|
||||||
return redirect(url_for("clients", nt="User has been deleted"))
|
return redirect(url_for("clients", nt="User has been deleted"))
|
||||||
|
1
requirements.txt
Normal file → Executable file
1
requirements.txt
Normal file → Executable file
@ -1,4 +1,5 @@
|
|||||||
outline-vpn-api
|
outline-vpn-api
|
||||||
|
kubernetes
|
||||||
PyYAML>=6.0.1
|
PyYAML>=6.0.1
|
||||||
Flask>=2.3.3
|
Flask>=2.3.3
|
||||||
flask-cors
|
flask-cors
|
9
static/layout.css
Normal file → Executable file
9
static/layout.css
Normal file → Executable file
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
body {
|
body {
|
||||||
color: #333;
|
color: #333;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -22,8 +23,8 @@ a {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.delete-button {
|
.delete-button {
|
||||||
background: #a20c0c;
|
background: #9d2c2c;
|
||||||
border: 2px solid #310404;
|
border: 1px solid #480b0b;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +159,10 @@ a {
|
|||||||
.server-item-unread {
|
.server-item-unread {
|
||||||
border-left: 6px solid #1b98f8;
|
border-left: 6px solid #1b98f8;
|
||||||
}
|
}
|
||||||
|
.server-item-broken {
|
||||||
|
border-left: 6px solid #880d06;
|
||||||
|
}
|
||||||
|
|
||||||
.server-item:hover {
|
.server-item:hover {
|
||||||
background: #d1d0d0;
|
background: #d1d0d0;
|
||||||
}
|
}
|
||||||
|
0
static/pure.css
Normal file → Executable file
0
static/pure.css
Normal file → Executable file
@ -1,25 +1,34 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
|
||||||
<title>{% block title %}Dashboard{% endblock %}</title>
|
<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='pure.css') }}">
|
||||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='layout.css') }}">
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='layout.css') }}">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<style>
|
</head>
|
||||||
.border {
|
|
||||||
border: 0px solid black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
<body>
|
||||||
padding: 10px;
|
|
||||||
}
|
<div id="layout" class="content pure-g">
|
||||||
.form-field {
|
<div id="nav" class="pure-u-1-3">
|
||||||
margin: 5px;
|
<a href="#" id="menuLink" class="nav-menu-button">Menu</a>
|
||||||
width: 100%;
|
|
||||||
}
|
<div class="nav-inner">
|
||||||
</style>
|
<button onclick="location.href='/';" style="cursor:pointer;" class="primary-button pure-button">OutFleet v.{{ VERSION }}</button>
|
||||||
|
|
||||||
|
<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="/clients" class="pure-menu-link">Clients</a></li>
|
||||||
|
<li class="pure-menu-item"><a href="/sync" class="pure-menu-link">Sync status</a></li>
|
||||||
|
</ul>
|
||||||
|
{{ VERSION }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
<!-- Script to make the Menu link work -->
|
<!-- Script to make the Menu link work -->
|
||||||
<!-- Just stripped down version of the js/ui.js script for the side-menu layout -->
|
<!-- Just stripped down version of the js/ui.js script for the side-menu layout -->
|
||||||
<script>
|
<script>
|
||||||
@ -71,31 +80,6 @@
|
|||||||
document.addEventListener('click', handleEvent);
|
document.addEventListener('click', handleEvent);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="pure-g">
|
|
||||||
<div class="pure-u-2-24 border" style="background: rgb(37, 42, 58);">
|
|
||||||
<div class="content">
|
|
||||||
<div class="nav-inner">
|
|
||||||
<h1 onclick="location.href='/';" style="cursor:pointer;" class="pure-button">OutFleet {{VERSION}}</h1>
|
|
||||||
<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="/clients" class="pure-menu-link">Clients</a></li>
|
|
||||||
<li class="pure-menu-item"><a href="/sync" class="pure-menu-link">Sync status</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-22-24 border">
|
|
||||||
<div class="content">
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% if nt %}
|
{% if nt %}
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" class="alertCheckbox" autocomplete="off" />
|
<input type="checkbox" class="alertCheckbox" autocomplete="off" />
|
||||||
@ -106,5 +90,4 @@
|
|||||||
</label>
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -2,142 +2,121 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="pure-u-3-24">
|
<div id="list" class="pure-u-1-3" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
|
||||||
<div class="server-item">
|
<div class="server-item pure-g">
|
||||||
<h1 class="server-content-title">Clients</h1>
|
<h1 class="server-content-title">Clients</h1>
|
||||||
</div>
|
</div>
|
||||||
<div onclick="location.href='/clients?add_client=True';" class="server-item server-add ">
|
|
||||||
<div class="">
|
|
||||||
+
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% for client, values in CLIENTS.items() %}
|
{% for client, values in CLIENTS.items() %}
|
||||||
<div onclick="location.href='/clients?selected_client={{ client }}';"
|
<div class="server-item server-item-{% if client == selected_client %}unread{% else %}selected{% endif %} pure-g">
|
||||||
class="server-item server-item-{% if client == selected_client %}unread{% else %}selected{% endif %} ">
|
<div class="pure-u-3-4" onclick="location.href='/clients?selected_client={{ client }}';">
|
||||||
<div class="">
|
|
||||||
<h5 class="server-name">{{ values["name"] }}</h5>
|
<h5 class="server-name">{{ values["name"] }}</h5>
|
||||||
<h4 class="server-info">Allowed {{ values["servers"]|length }} server{% if values["servers"]|length >1
|
<h4 class="server-info">Allowed {{ values["servers"]|length }} server{% if values["servers"]|length >1 %}s{%endif%}</h4>
|
||||||
%}s{%endif%}</h4>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<div onclick="location.href='/clients?add_client=True';" class="server-item server-add pure-g">
|
||||||
|
<div class="pure-u-1">
|
||||||
|
+
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-20-24">
|
|
||||||
{% if add_client %}
|
|
||||||
|
|
||||||
<div class="server-content-header">
|
{% if add_client %}
|
||||||
<div class="">
|
<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>
|
<h1 class="server-content-title">Add new client</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="server-content-body">
|
<div class="server-content-body">
|
||||||
<form action="/add_client" class="pure-form pure-form-stacked" method="POST">
|
<form action="/add_client" class="pure-form pure-form-stacked" method="POST">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<input type="text" class="form-field pure-u-8-24" name="name" required placeholder="Name" />
|
<div class="pure-g">
|
||||||
<input type="text" class="form-field pure-u-8-24" name="comment" placeholder="Comment" />
|
<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 %}
|
{% for server in SERVERS %}
|
||||||
<label class="pure-checkbox" for="option{{loop.index0}}">
|
<label class="pure-checkbox" for="option{{loop.index0}}">{{server.info()["name"]}}
|
||||||
<input type="checkbox" id="option{{loop.index0}}" name="servers"
|
<input type="checkbox" id="option{{loop.index0}}" name="servers" value="{{server.info()['local_server_id']}}"></label>
|
||||||
value="{{server.info()['local_server_id']}}">{{server.info()["name"]}}</label>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<button type="submit" class="pure-button pure-button-primary button">Add client</button>
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="pure-button pure-input-1 pure-button-primary">Add</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if selected_client and not add_client %}
|
{% if selected_client and not add_client %}
|
||||||
{% set client = CLIENTS[selected_client] %}
|
{% set client = CLIENTS[selected_client] %}
|
||||||
<div class="pure-g">
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1-2">
|
||||||
<div class="server-content-header">
|
<div class="server-content-header pure-g">
|
||||||
<div class="">
|
<div class="pure-u-1-2">
|
||||||
<h1 class="server-content-title">{{client['name']}}</h1>
|
<h1 class="server-content-title">{{client['name']}}</h1>
|
||||||
<h4 class="server-info">{{ selected_client }}</h4>
|
<h4 class="server-info">{{ client['comment'] }}</h4>
|
||||||
{% if client['comment'] != "" %}<h4 class="server-info">{{ client['comment'] }}</h4>{% endif %}
|
<h4 class="server-info">id {{ selected_client }}</h4>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-g">
|
|
||||||
<div class="pure-u-6-24">
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="server-content-body">
|
<div class="server-content-body">
|
||||||
<form action="/add_client" class="pure-form pure-form-aligned" method="POST">
|
<form action="/add_client" class="pure-form pure-form-stacked" method="POST">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<div class="pure-g">
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<input type="text" class="form-field" name="name" required value="{{client['name']}}" />
|
<input type="text" class="pure-u-1" name="name" required value="{{client['name']}}"/>
|
||||||
<input type="hidden" class="form-field" name="old_name" required
|
<input type="hidden" class="pure-u-1" name="old_name" required value="{{client['name']}}"/>
|
||||||
value="{{client['name']}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<input type="text" class="form-field" name="comment" value="{{client['comment']}}" />
|
<input type="text" class="pure-u-1" name="comment" value="{{client['comment']}}"/>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" class="form-field" name="user_id" value="{{selected_client}}" />
|
<input type="hidden" class="pure-u-1" name="user_id" value="{{selected_client}}"/>
|
||||||
|
|
||||||
<div class="pure-checkbox">
|
<div class="pure-checkbox">
|
||||||
<p>Allow access to:</p>
|
<p>Allow access to:</p>
|
||||||
{% for server in SERVERS %}
|
{% for server in SERVERS %}
|
||||||
<label class="pure-checkbox" for="option{{loop.index0}}">
|
<label class="pure-checkbox" for="option{{loop.index0}}">{{server.info()["name"]}}{% if server.info()['local_server_id'] in client['servers'] %} ( Used {% for key in server.data["keys"] %}{% if key.name == client['name'] %}{{ (key.used_bytes if key.used_bytes else 0) | filesizeformat }}{% endif %}{% endfor %}){%endif%}
|
||||||
<input {% if server.info()['local_server_id'] in client['servers'] %}checked{%endif%}
|
<input
|
||||||
type="checkbox" id="option{{loop.index0}}" name="servers"
|
{% if server.info()['local_server_id'] in client['servers'] %}checked{%endif%}
|
||||||
value="{{server.info()['local_server_id']}}">{{server.info()["name"]}}
|
type="checkbox" id="option{{loop.index0}}" name="servers" value="{{server.info()['local_server_id']}}"></label>
|
||||||
{% if server.info()['local_server_id'] in client['servers'] %} ( Used {% for key in
|
|
||||||
server.data["keys"] %}{% if key.name == client['name'] %}{{ (key.used_bytes if
|
|
||||||
key.used_bytes else 0) | filesizeformat }}{% endif %}{% endfor %}){%endif%}</label>
|
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="pure-button pure-button-primary button">Save and Apply</button>
|
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="pure-button pure-input-1 pure-button-primary">Save and apply</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<form action="/del_client" class="pure-form pure-form-stacked" method="POST">
|
|
||||||
<input type="hidden" name="name" required value="{{client['name']}}" />
|
|
||||||
<input type="hidden" name="user_id" value="{{selected_client}}" />
|
|
||||||
|
|
||||||
<label for="really" class="pure-radio">
|
|
||||||
<button type="submit" id="really"
|
|
||||||
class="pure-button pure-button-primary delete-button button">Delete Client 🔒<input
|
|
||||||
type="checkbox" id="agree" name="agree" required></button></label>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="" class="pure-u-18-24">
|
|
||||||
<div class="pure-u-9-24">
|
|
||||||
<div>
|
<div>
|
||||||
<h3>Invite text</h3>
|
<h3>Invite text</h3><hr>
|
||||||
|
<textarea style="width: 100%; rows=10">
|
||||||
<textarea style="width: 96%; height: 360px; resize:none" disabled>
|
|
||||||
Install OutLine VPN. Copy and paste below keys to OutLine client.
|
Install OutLine VPN. Copy and paste below keys to OutLine client.
|
||||||
Same keys will work simultaneously on many devices.
|
Same keys will work simultaneously on many devices.
|
||||||
{% for server in SERVERS -%}
|
{% for server in SERVERS -%}
|
||||||
|
|
||||||
{% if server.info()['local_server_id'] in client['servers'] %}
|
{% if server.info()['local_server_id'] in client['servers'] %}
|
||||||
{{server.info()['name']}}
|
{{server.info()['name']}}
|
||||||
```{% for key in server.data["keys"] %}{% if key.key_id == client['name'] %}ssconf://{{
|
```{% for key in server.data["keys"] %}{% if key.key_id == client['name'] %}ssconf://{{ dynamic_hostname }}/dynamic/{{server.info()['name']}}/{{selected_client}}#{{server.info()['comment']}}{% endif %}{% endfor %}```
|
||||||
dynamic_hostname
|
|
||||||
}}/dynamic/{{server.info()['name']}}/{{selected_client}}#{{server.info()['comment']}}{% endif %}{%
|
|
||||||
endfor %}```
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%- endfor -%}
|
{%- endfor -%}</textarea>
|
||||||
</textarea>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<hr>
|
||||||
<div class="pure-u-14-24">
|
<div style="padding-top: 15px; padding-bottom: 15px">
|
||||||
|
<div class="pure-u-1">
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-g">
|
|
||||||
<div class="pure-u-1 content">
|
|
||||||
<h3>Dynamic Access Keys</h3>
|
<h3>Dynamic Access Keys</h3>
|
||||||
<table class="pure-table" style="width: 100%">
|
<table class="pure-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Server</th>
|
<th>Server</th>
|
||||||
@ -149,11 +128,8 @@ endfor %}```
|
|||||||
{% if server.info()['local_server_id'] in client['servers'] %}
|
{% if server.info()['local_server_id'] in client['servers'] %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ server.info()['name'] }}</td>
|
<td>{{ server.info()['name'] }}</td>
|
||||||
<td style="font-size: 10pt">
|
<td>
|
||||||
{% for key in server.data["keys"] %}{% if key.key_id == client['name'] %}ssconf://{{
|
<p style="font-size: 10pt">{% for key in server.data["keys"] %}{% if key.key_id == client['name'] %}ssconf://{{ dynamic_hostname }}/dynamic/{{server.info()['name']}}/{{selected_client}}#{{server.info()['comment']}}{% endif %}{% endfor %}</p>
|
||||||
dynamic_hostname
|
|
||||||
}}/dynamic/{{server.info()['name']}}/{{selected_client}}#{{server.info()['comment']}}{%
|
|
||||||
endif %}{% endfor %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -161,9 +137,9 @@ endfor %}```
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 content">
|
<div class="pure-u-1 pure-u-md-1">
|
||||||
<h3>SS Links</h3>
|
<h3>SS Links</h3>
|
||||||
<table class="pure-table" style="width: 100%">
|
<table class="pure-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Server</th>
|
<th>Server</th>
|
||||||
@ -175,9 +151,8 @@ endfor %}```
|
|||||||
{% if server.info()['local_server_id'] in client['servers'] %}
|
{% if server.info()['local_server_id'] in client['servers'] %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ server.info()['name'] }}</td>
|
<td>{{ server.info()['name'] }}</td>
|
||||||
<td style="font-size: 10pt">
|
<td>
|
||||||
{% for key in server.data["keys"] %}{% if key.key_id == client['name'] %}{{
|
<pre style="font-size: 10pt">{% for key in server.data["keys"] %}{% if key.key_id == client['name'] %}{{ key.access_url }}{% endif %}{% endfor %}</pre>
|
||||||
key.access_url }}{% endif %}{% endfor %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -185,7 +160,15 @@ endfor %}```
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<form action="/del_client" class="pure-form pure-form-stacked" method="POST">
|
||||||
|
<input type="hidden" class="pure-u-1" name="name" required value="{{client['name']}}"/>
|
||||||
|
<input type="hidden" class="pure-u-1" name="user_id" value="{{selected_client}}"/>
|
||||||
|
<button type="submit" class="pure-button button-error pure-input-1 ">Delete</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,9 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pure-g">
|
|
||||||
<div class="pure-u-4-24">
|
<div id="list" class="pure-u-1-3" xmlns="http://www.w3.org/1999/html">
|
||||||
<div class="server-item ">
|
<div class="server-item pure-g">
|
||||||
<h1 class="server-content-title">Servers</h1>
|
<h1 class="server-content-title">Servers</h1>
|
||||||
</div>
|
</div>
|
||||||
{% for server in SERVERS %}
|
{% for server in SERVERS %}
|
||||||
@ -13,57 +13,57 @@
|
|||||||
{% set list_ns.total_bytes = list_ns.total_bytes + key.used_bytes %}
|
{% set list_ns.total_bytes = list_ns.total_bytes + key.used_bytes %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div
|
<div class="server-item server-item-{% if loop.index0 == selected_server|int %}unread{% else %}selected{% endif %} pure-g">
|
||||||
class="server-item server-item-{% if loop.index0 == selected_server|int %}unread{% else %}selected{% endif %} ">
|
<div class="pure-u-3-4" onclick="location.href='/?selected_server={{loop.index0}}';">
|
||||||
<div onclick="location.href='/?selected_server={{loop.index0}}';">
|
|
||||||
<h5 class="server-name">{{ server.info()["name"] }}</h5>
|
<h5 class="server-name">{{ server.info()["name"] }}</h5>
|
||||||
<h4 class="server-info">API {{ '/'.join(server.info()["url"].split('/')[0:-1]).split("://")[1] }}</h4>
|
<h4 class="server-info">{{ '/'.join(server.info()["url"].split('/')[0:-1]) }}</h4>
|
||||||
<h4 class="server-info">Client Port: {{ server.info()["port_for_new_access_keys"] }}</h4>
|
<h4 class="server-info">Port {{ server.info()["port_for_new_access_keys"] }}</h4>
|
||||||
<h4 class="server-info">Hostname: {{ server.info()["hostname_for_access_keys"] }}</h4>
|
<h4 class="server-info">Hostname {{ server.info()["hostname_for_access_keys"] }}</h4>
|
||||||
<h4 class="server-info">Traffic: {{ list_ns.total_bytes | filesizeformat }}</h4>
|
<h4 class="server-info">Traffic: {{ list_ns.total_bytes | filesizeformat }}</h4>
|
||||||
<h4 class="server-info">Version: {{ server.info()["version"] }}</h4>
|
<h4 class="server-info">v.{{ server.info()["version"] }}</h4>
|
||||||
<p class="server-comment">
|
<p class="server-comment">
|
||||||
{{ server.info()["comment"] }}
|
{{ server.info()["comment"] }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div onclick="location.href='/?add_server=True';" class="server-item server-add ">
|
<div onclick="location.href='/?add_server=True';" class="server-item server-add pure-g">
|
||||||
<div class="">
|
<div class="pure-u-1">
|
||||||
+
|
+
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-u-19-24">
|
|
||||||
{% if add_server %}
|
{% if add_server %}
|
||||||
<div class="server-content-header">
|
<div class="pure-u-1-3">
|
||||||
<div class="">
|
<div class="server-content-header pure-g">
|
||||||
|
<div class="pure-u-1-2">
|
||||||
<h1 class="server-content-title">Add new server</h1>
|
<h1 class="server-content-title">Add new server</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="server-content-body">
|
<div class="server-content-body">
|
||||||
<form action="/add_server" class="pure-form pure-form-stacked" method="POST">
|
<form action="/add_server" class="pure-form pure-form-stacked" method="POST">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="">
|
<div class="pure-g">
|
||||||
<div class="form-field">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<input type="text" class="form-field" name="url" placeholder="Server management URL" />
|
<input type="text" class="pure-u-23-24" name="url" placeholder="Server management URL"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<input type="text" class="form-field" name="cert" placeholder="Certificate" />
|
<input type="text"class="pure-u-23-24" name="cert" placeholder="Certificate"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<input type="text" class="form-field" name="comment" placeholder="Comment" />
|
<input type="text" class="pure-u-23-24" name="comment" placeholder="Comment"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="pure-button pure-button-primary button">Add server</button>
|
<button type="submit" class="pure-button pure-input-1 pure-button-primary">Add</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if SERVERS|length != 0 and not add_server %}
|
{% if SERVERS|length != 0 and not add_server %}
|
||||||
|
|
||||||
{% if selected_server is none %}
|
{% if selected_server is none %}
|
||||||
@ -71,11 +71,10 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% set server = SERVERS[selected_server|int] %}
|
{% set server = SERVERS[selected_server|int] %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="pure-u-19-24">
|
<div id="main" class="pure-u-1">
|
||||||
<div class="">
|
|
||||||
<div class="server-content">
|
<div class="server-content">
|
||||||
<div class="server-content-header ">
|
<div class="server-content-header pure-g">
|
||||||
<div class="">
|
<div class="pure-u-1-2">
|
||||||
<h1 class="server-content-title">{{server.info()["name"]}}</h1>
|
<h1 class="server-content-title">{{server.info()["name"]}}</h1>
|
||||||
<p class="server-content-subtitle">
|
<p class="server-content-subtitle">
|
||||||
<span>v.{{server.info()["version"]}} {{server.info()["local_server_id"]}}</span>
|
<span>v.{{server.info()["version"]}} {{server.info()["local_server_id"]}}</span>
|
||||||
@ -93,75 +92,59 @@
|
|||||||
<div class="server-content-body">
|
<div class="server-content-body">
|
||||||
<h3>Clients: {{ server.info()['keys']|length }}</h3>
|
<h3>Clients: {{ server.info()['keys']|length }}</h3>
|
||||||
<h3>Total traffic: {{ ns.total_bytes | filesizeformat }}</h3>
|
<h3>Total traffic: {{ ns.total_bytes | filesizeformat }}</h3>
|
||||||
<form class="pure-form pure-form-aligned" method="POST">
|
<form class="pure-form pure-form-stacked" method="POST">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="">
|
<div class="pure-g">
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<label for="name">Server Name</br>Must be unique. Used for Dynamic Link
|
<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>
|
||||||
generation.</label>
|
<input type="text" id="name" class="pure-u-23-24" name="name" value="{{server.info()['name']}}"/>
|
||||||
<input class="form-field" type="text" id="name" class="" name="name"
|
|
||||||
value="{{server.info()['name']}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<label for="comment">Comment</br>Will be used as "Server name" in client
|
<label for="comment">Comment</br>This value will be used as "Server name" in client app.</label>
|
||||||
app.</label>
|
<input type="text" id="comment" class="pure-u-23-24" name="comment" value="{{server.info()['comment']}}"/>
|
||||||
<input class="form-field" type="text" id="comment" class="" name="comment"
|
|
||||||
value="{{server.info()['comment']}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<label for="port_for_new_access_keys">Port For New Access Keys</label>
|
<label for="port_for_new_access_keys">Port For New Access Keys</label>
|
||||||
<input class="form-field" type="text" id="port_for_new_access_keys" class=""
|
<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']}}"/>
|
||||||
name="port_for_new_access_keys"
|
|
||||||
value="{{server.info()['port_for_new_access_keys']}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<label for="hostname_for_access_keys">Hostname For Access Keys</label>
|
<label for="hostname_for_access_keys">Hostname For Access Keys</label>
|
||||||
<input class="form-field" type="text" id="hostname_for_access_keys" class=""
|
<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']}}"/>
|
||||||
name="hostname_for_access_keys"
|
|
||||||
value="{{server.info()['hostname_for_access_keys']}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<label for="url">Server URL</label>
|
<label for="url">Server URL</label>
|
||||||
<input class="form-field" type="text" readonly id="url" class="" name="url"
|
<input type="text" readonly id="url" class="pure-u-23-24" name="url" value="{{server.info()['url']}}"/>
|
||||||
value="{{server.info()['url']}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<label for="cert">Server Access Certificate</label>
|
<label for="cert">Server Access Certificate</label>
|
||||||
<input class="form-field" type="text" readonly id="cert" class="" name="cert"
|
<input type="text" readonly id="cert" class="pure-u-23-24" name="cert" value="{{server.info()['cert']}}"/>
|
||||||
value="{{server.info()['cert']}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
Created {{format_timestamp(server.info()['created_timestamp_ms']) }}
|
<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>
|
</div>
|
||||||
<input class="form-field" type="hidden" readonly id="server_id" class="" name="server_id"
|
<input type="hidden" readonly id="server_id" class="pure-u-23-24" name="server_id" value="{{server.info()['local_server_id']}}"/>
|
||||||
value="{{server.info()['local_server_id']}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<p>Share anonymous metrics</p>
|
<p>Share anonymous metrics</p>
|
||||||
<label for="metrics_enabled" class="pure-radio">
|
<label for="metrics_enabled" class="pure-radio">
|
||||||
<input type="radio" id="metrics_enabled" name="metrics" value="True" {% if
|
<input type="radio" id="metrics_enabled" name="metrics" value="True" {% if server.info()['metrics_enabled'] == True %}checked{% endif %} /> Enable
|
||||||
server.info()['metrics_enabled']==True %}checked{% endif %} /> Enable
|
|
||||||
</label>
|
</label>
|
||||||
<label for="metrics_disabled" class="pure-radio">
|
<label for="metrics_disabled" class="pure-radio">
|
||||||
<input type="radio" id="metrics_disabled" name="metrics" value="False" {% if
|
<input type="radio" id="metrics_disabled" name="metrics" value="False" {% if server.info()['metrics_enabled'] == False %}checked{% endif %} /> Disable
|
||||||
server.info()['metrics_enabled']==False %}checked{% endif %} /> Disable
|
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" class="pure-button pure-button-primary button">Save and apply</button>
|
<button type="submit" class="pure-button pure-button-primary button">Save and apply</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<form action="/del_server" method="post">
|
<form action="/del_server" method="post">
|
||||||
|
<input type="hidden" name="local_server_id" value="{{ server.info()["local_server_id"] }}">
|
||||||
<input type="hidden" id="really" value="{{ server.info()[" local_server_id"] }}">
|
<button type="submit" class="pure-button pure-button-primary delete-button button">Delete Server</button>
|
||||||
<label for="really" class="pure-radio">
|
<input type="checkbox" id="agree" name="agree" required>
|
||||||
<button type="submit" class="pure-button pure-button-primary delete-button button">Delete
|
|
||||||
Server 🔒<input type="checkbox" id="agree" name="agree" required></button></label>
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,32 +1,17 @@
|
|||||||
{% extends "base.html" %}
|
<h1>Last sync log</h1>
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
||||||
|
|
||||||
<!-- and it's easy to individually load additional languages -->
|
|
||||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script> -->
|
|
||||||
|
|
||||||
<script>hljs.highlightAll();</script>
|
|
||||||
|
|
||||||
|
|
||||||
<h1>Last logs</h1>
|
|
||||||
<form action="/sync" class="pure-form pure-form-stacked" method="POST">
|
<form action="/sync" class="pure-form pure-form-stacked" method="POST">
|
||||||
<button type="submit" class="pure-button pure-button-primary button">Sync now</button>
|
<p>Wipe ALL keys on ALL servers?</p>
|
||||||
<p>Also wipe ALL keys on ALL servers? Use in case of inconsistency.</p>
|
|
||||||
<label for="no_wipe" class="pure-radio">
|
<label for="no_wipe" class="pure-radio">
|
||||||
<input type="radio" id="no_wipe" name="wipe" value="no_wipe" checked /> No
|
<input type="radio" id="no_wipe" name="wipe" value="no_wipe" checked /> No
|
||||||
</label>
|
</label>
|
||||||
<label for="do_wipe" class="pure-radio">
|
<label for="do_wipe" class="pure-radio">
|
||||||
<input type="radio" id="do_wipe" name="wipe" value="all" /> Yes
|
<input type="radio" id="do_wipe" name="wipe" value="all" /> Yes
|
||||||
</label>
|
</label>
|
||||||
|
<button type="submit" class="pure-button button-error pure-input-1 ">Sync now</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<pre style="height: 600px; overflow: scroll;">
|
<pre>
|
||||||
<code class="language-c">
|
<code>
|
||||||
{% for line in lines %}{{ line }}{% endfor %}
|
{% for line in lines %}{{ line }}{% endfor %}
|
||||||
</code>
|
</code>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
{% endblock %}
|
|
Reference in New Issue
Block a user