mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-07-07 01:24:06 +00:00
redesign and k8s support
This commit is contained in:
34
k8s.py
Normal file
34
k8s.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import base64
|
||||||
|
import json
|
||||||
|
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)
|
||||||
|
|
||||||
|
config.load_incluster_config()
|
||||||
|
|
||||||
|
v1 = client.CoreV1Api()
|
||||||
|
|
||||||
|
NAMESPACE = ""
|
||||||
|
|
||||||
|
log.info("Checking for Kubernetes environment")
|
||||||
|
try:
|
||||||
|
with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace") as f:
|
||||||
|
NAMESPACE = f.read().strip()
|
||||||
|
except IOError:
|
||||||
|
log.info("Kubernetes environment not detected")
|
||||||
|
pass
|
36
main.py
36
main.py
@ -6,10 +6,13 @@ 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
|
||||||
|
|
||||||
|
|
||||||
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@ -34,10 +37,17 @@ parser.add_argument(
|
|||||||
default="/usr/local/etc/outfleet/config.yaml",
|
default="/usr/local/etc/outfleet/config.yaml",
|
||||||
help="Config file location",
|
help="Config file location",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--k8s",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Kubernetes Outline server discovery",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
CFG_PATH = args.config
|
CFG_PATH = args.config
|
||||||
|
|
||||||
SERVERS = list()
|
SERVERS = list()
|
||||||
|
BROKEN_SERVERS = list()
|
||||||
CLIENTS = dict()
|
CLIENTS = dict()
|
||||||
VERSION = '3'
|
VERSION = '3'
|
||||||
HOSTNAME = ""
|
HOSTNAME = ""
|
||||||
@ -54,12 +64,30 @@ 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 get_config():
|
||||||
|
if not args.k8s:
|
||||||
|
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}")
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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 = dict()
|
||||||
try:
|
try:
|
||||||
@ -90,6 +118,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 +131,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,
|
||||||
)
|
)
|
||||||
|
@ -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
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
.content {
|
.content {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field {
|
.form-field {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- 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>
|
||||||
@ -56,20 +58,20 @@
|
|||||||
toggleClass(elements.menu, active);
|
toggleClass(elements.menu, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEvent(e) {
|
// function handleEvent(e) {
|
||||||
var elements = getElements();
|
// var elements = getElements();
|
||||||
|
|
||||||
if (e.target.id === elements.menuLink.id) {
|
// if (e.target.id === elements.menuLink.id) {
|
||||||
toggleMenu();
|
// toggleMenu();
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
} else if (elements.menu.className.indexOf('active') !== -1) {
|
// } else if (elements.menu.className.indexOf('active') !== -1) {
|
||||||
toggleMenu();
|
// toggleMenu();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
// document.addEventListener('DOMContentLoaded', function () {
|
||||||
document.addEventListener('click', handleEvent);
|
// document.addEventListener('click', handleEvent);
|
||||||
});
|
// });
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -79,7 +81,8 @@
|
|||||||
<div class="pure-u-2-24 border" style="background: rgb(37, 42, 58);">
|
<div class="pure-u-2-24 border" style="background: rgb(37, 42, 58);">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="nav-inner">
|
<div class="nav-inner">
|
||||||
<h1 onclick="location.href='/';" style="cursor:pointer;" class="pure-button">OutFleet {{VERSION}}</h1>
|
<h1 onclick="location.href='/';" style="cursor:pointer;" class="pure-button">OutFleet {{VERSION}}
|
||||||
|
</h1>
|
||||||
<ul class="pure-menu-list">
|
<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">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="/clients" class="pure-menu-link">Clients</a></li>
|
||||||
|
@ -28,6 +28,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% for broken_server in BROKEN_SERVERS %}
|
||||||
|
{% set config = broken_server["config"] %}
|
||||||
|
{% set error = broken_server["error"] %}
|
||||||
|
<div class="server-item server-item-broken">
|
||||||
|
<div onclick="location.href='/?selected_server={{ broken_server['id'] }}&broken=true';">
|
||||||
|
<h5 class="server-name">{{ config.get("name", "None") }}</h5>
|
||||||
|
<h4 class="server-info">API {{ '/'.join(config.get("url", "None").split('/')[0:-1]).split("://")[1] }}
|
||||||
|
</h4>
|
||||||
|
<h4 class="server-info">Client Port: N/D</h4>
|
||||||
|
<h4 class="server-info">Hostname: N/D</h4>
|
||||||
|
<h4 class="server-info">Traffic: N/D</h4>
|
||||||
|
<h4 class="server-info">Version: N/D</h4>
|
||||||
|
<p class="server-comment">
|
||||||
|
{{ config.get("comment", "None") }}
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<div style="padding: 5px; color: #6a4545; background-color: #f6e9e9; border-radius: 5px;">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% 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 ">
|
||||||
<div class="">
|
<div class="">
|
||||||
+
|
+
|
||||||
@ -71,7 +93,9 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% set server = SERVERS[selected_server|int] %}
|
{% set server = SERVERS[selected_server|int] %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="pure-u-19-24">
|
<div class="pure-u-19-24">
|
||||||
|
{% if not is_broken %}
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="server-content">
|
<div class="server-content">
|
||||||
<div class="server-content-header ">
|
<div class="server-content-header ">
|
||||||
@ -81,7 +105,6 @@
|
|||||||
<span>v.{{server.info()["version"]}} {{server.info()["local_server_id"]}}</span>
|
<span>v.{{server.info()["version"]}} {{server.info()["local_server_id"]}}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% set ns = namespace(total_bytes=0) %}
|
{% set ns = namespace(total_bytes=0) %}
|
||||||
@ -122,19 +145,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="">
|
||||||
<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 class="form-field" type="text" disabled id="url" class="" name="url"
|
||||||
value="{{server.info()['url']}}" />
|
value="{{server.info()['url']}}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="">
|
||||||
<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 class="form-field" type="text" disabled id="cert" class="" name="cert"
|
||||||
value="{{server.info()['cert']}}" />
|
value="{{server.info()['cert']}}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="">
|
||||||
Created {{format_timestamp(server.info()['created_timestamp_ms']) }}
|
Created {{format_timestamp(server.info()['created_timestamp_ms']) }}
|
||||||
</div>
|
</div>
|
||||||
<input class="form-field" type="hidden" readonly id="server_id" class="" name="server_id"
|
<input class="form-field" type="hidden" readonly id="server_id" class=""
|
||||||
value="{{server.info()['local_server_id']}}" />
|
name="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">
|
||||||
@ -152,14 +175,85 @@
|
|||||||
|
|
||||||
<input type="hidden" id="really" value="{{ server.info()[" local_server_id"] }}">
|
<input type="hidden" id="really" value="{{ server.info()[" local_server_id"] }}">
|
||||||
<label for="really" class="pure-radio">
|
<label for="really" class="pure-radio">
|
||||||
<button type="submit" class="pure-button pure-button-primary delete-button button">Delete
|
<button type="submit" id="delete_server"
|
||||||
|
class="pure-button pure-button-primary delete-button button">Delete
|
||||||
Server 🔒<input type="checkbox" id="agree" name="agree" required></button></label>
|
Server 🔒<input type="checkbox" id="agree" name="agree" required></button></label>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% for server in BROKEN_SERVERS %}
|
||||||
|
{% if server["id"] == selected_server %}
|
||||||
|
{% set config_block = server["config"] %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{{config_block}} {{id}}
|
||||||
|
<div class="">
|
||||||
|
<div class="server-content">
|
||||||
|
<div class="server-content-header ">
|
||||||
|
<div class="">
|
||||||
|
<h1 class="server-content-title">{{config_block["name"]}}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="server-content-body">
|
||||||
|
<form class="pure-form pure-form-aligned" method="POST">
|
||||||
|
<fieldset>
|
||||||
|
<div class="">
|
||||||
|
<div class="">
|
||||||
|
<label for="name">Server Name</br>Must be unique. Used for Dynamic Link
|
||||||
|
generation.</label>
|
||||||
|
<input class="form-field" disabled type="text" id="name" class="" name="name"
|
||||||
|
value="No connection" />
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<label for="comment">Comment</br>Will be used as "Server name" in client
|
||||||
|
app.</label>
|
||||||
|
<input class="form-field" type="text" id="comment" class="" name="comment"
|
||||||
|
value="{{config_block['comment']}}" />
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<label for="port_for_new_access_keys">Port For New Access Keys</label>
|
||||||
|
<input disabled class="form-field" type="text" id="port_for_new_access_keys"
|
||||||
|
class="" name="port_for_new_access_keys" value="No connection" />
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<label for="hostname_for_access_keys">Hostname For Access Keys</label>
|
||||||
|
<input disabled class="form-field" type="text" id="hostname_for_access_keys"
|
||||||
|
class="" name="hostname_for_access_keys" value="No connection" />
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<label for="url">Server URL</label>
|
||||||
|
<input class="form-field" type="text" id="url" class="" name="url"
|
||||||
|
value="{{config_block['url']}}" />
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<label for="cert">Server Access Certificate</label>
|
||||||
|
<input class="form-field" type="text" id="cert" class="" name="cert"
|
||||||
|
value="{{config_block['cert']}}" />
|
||||||
|
</div>
|
||||||
|
<input class="form-field" readonly id="server_id" class=""
|
||||||
|
name="server_id" value="{{ selected_server }}" />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<form action="/del_server" method="post">
|
||||||
|
|
||||||
|
<input type="hidden" id="really" value="{{ selected_server }}">
|
||||||
|
<label for="really" class="pure-radio">
|
||||||
|
<button type="submit" id="delete_server"
|
||||||
|
class="pure-button pure-button-primary delete-button button">Delete
|
||||||
|
Server 🔒<input type="checkbox" id="agree" name="agree" required></button></label>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
Reference in New Issue
Block a user