From 99809e113941b8f7ecfc757a794db902c1a3d103 Mon Sep 17 00:00:00 2001 From: lare Date: Wed, 21 Dec 2022 21:21:57 +0100 Subject: [PATCH] [node] implement everything... --- .vscode/launch.json | 23 +- nodes/.gitignore | 3 + nodes/main.py | 405 ++++++++++++++++++++++++ nodes/node.config.sample.jsonc | 21 ++ nodes/templates/bgp-peer.template.conf | 36 +++ nodes/templates/wireguard.template.conf | 13 + web/backend/config.sample.jsonc | 2 +- web/backend/peering_manager.py | 22 +- web/frontend/static/style.css | 14 +- 9 files changed, 527 insertions(+), 12 deletions(-) create mode 100644 nodes/.gitignore create mode 100644 nodes/main.py create mode 100644 nodes/node.config.sample.jsonc create mode 100644 nodes/templates/bgp-peer.template.conf create mode 100644 nodes/templates/wireguard.template.conf diff --git a/.vscode/launch.json b/.vscode/launch.json index a3bf90a..6829b2b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,12 +1,33 @@ { "configurations": [ + { + "name": "Python: Flask-node", + "type": "python", + "request": "launch", + "module": "flask", + "cwd": "${workspaceFolder}/nodes/", + "env": { + "FLASK_APP": "main.py", + "FLASK_DEBUG": "1", + "FLASK_RUN_HOST": "::", + "FLASK_RUN_PORT": "8142" + }, + "args": ["run", "--no-debugger"], + "jinja": true, + "justMyCode": true + }, { "name": "Python: Flask-backend", "type": "python", "request": "launch", "module": "flask", "cwd": "${workspaceFolder}/web/", - "env": { "FLASK_APP": "backend/main.py", "FLASK_DEBUG": "1", "FLASK_RUN_HOST": "0.0.0.0", "FLASK_RUN_PORT": "8042" }, + "env": { + "FLASK_APP": "backend/main.py", + "FLASK_DEBUG": "1", + "FLASK_RUN_HOST": "0.0.0.0", + "FLASK_RUN_PORT": "8042" + }, "args": ["run", "--no-debugger"], "jinja": true, "justMyCode": true diff --git a/nodes/.gitignore b/nodes/.gitignore new file mode 100644 index 0000000..7d8b5ac --- /dev/null +++ b/nodes/.gitignore @@ -0,0 +1,3 @@ +venv +peerings.json +config.json \ No newline at end of file diff --git a/nodes/main.py b/nodes/main.py new file mode 100644 index 0000000..253de93 --- /dev/null +++ b/nodes/main.py @@ -0,0 +1,405 @@ +from flask import Flask, request, session, Response, render_template +from flask_restful import Resource, Api, reqparse +#import pandas as pd +#import ast +import json +import os +import base64 +import logging +import random +import time +from functools import wraps +import subprocess + +app = Flask(__name__) +api = Api(app) + +# used as default value for create/update peering + + +class NotSpecified: + pass + + +class Config (dict): + def __init__(self, configfile: str = None): + if configfile: + self.configfile = configfile + else: + if os.path.exists("./node.config.json"): + self.configfile = "./node.config.json" + elif os.path.exists("./config.json"): + self.configfile = "./config.json" + elif os.path.exists("/etc/dn42-autopeer/node.config.json"): + self.configfile = "/etc/dn42-autopeer/node.config.json" + else: + raise FileNotFoundError( + "no config file found in ./node.config.json, ./config.json or /etc/dn42-autopeer/config.json") + self._load_config() + self.keys = self._config.keys + #self.__getitem__ = self._config.__getitem__ + super().__init__(self) + + def __contains__(self, o): + return self._config.__contains__(o) + + def __delitem__(self, v): + raise NotImplementedError() + super().__delitem__(self, v) + + def __getitem__(self, k): + return self._config[k] + + def _load_config(self): + with open(self.configfile) as cf: + try: + self._config = json.load(cf) + except json.decoder.JSONDecodeError: + raise SyntaxError(f"no valid JSON found in '{cf.name}'") + print(self._config) + if not "debug-mode" in self._config: + self._config["debug-mode"] = False + + if not "peerings" in self._config: + self._config["peerings"] = "./peerings" + + if not "wg-configs" in self._config: + self._config["wg-configs"] = "/etc/wireguard/" + + if not os.path.exists(self._config["wg-configs"]): + raise FileNotFoundError(f"specified wg-configs '{self._config['wg-configs']}' isn't a directory/doesn't exist") + + if not "wg-commands" in self._config: + raise KeyError("wg-commands not specified") + + # check if required keys is a subset of the specified keys + if not set(["enable", "disable","up","down"]) <= set(self._config["wg-commands"].keys()): + raise KeyError("at least one of 'enable','disable','up','down' in 'wg-commands' not specified") + + if not "bird-peers" in self._config: + self._config["bird-peers"] = "/etc/bird/peers/" + if not os.path.exists(self._config["bird-peers"]): + raise FileNotFoundError(f"specified bird-peers '{self._config['bird-peers']}' isn't a directory/doesn't exist") + + if not "bird-reload" in self._config: + self._config["bird-reload"] = "birdc configure" + + if not "templates" in self._config: + self._config["templates"] = "./templates" + + logging.info(self._config) + + +class PeeringManager: + def __init__(self, config): + self.__config = config + self.__peering_file = config["peerings"] + + self.__load_peerings() + + def __load_peerings(self): + if not os.path.exists(self.__peering_file): + with open(self.__peering_file, "x") as p: + json.dump({}, p) + try: + with open(self.__peering_file, "r") as p: + self.peerings = json.load(p) + except json.decoder.JSONDecodeError: + with open(self.__peering_file, "w") as p: + json.dump({}, p) + with open(self.__peering_file, "r") as p: + self.peerings = json.load(p) + + # self.peerings = {} + # missing_peerings = False + # for peering in self._peerings: + # if os.path.exists(f"{self._peering_dir}/{peering}.json"): + # with open(f"{self._peering_dir}/{peering}.json") as peer_cfg: + # self.peerings[peering] = json.load(peer_cfg) + # else: + # logging.warning(f"peering with id {peering} doesn't exist. removing reference in `{self._peering_dir}/peerings.json`") + # self._peerings.remove(peering) + # missing_peerings = True + # if missing_peerings: + # with open(f"{self._peering_dir}/peerings.json","w") as p: + # json.dump(self._peerings, p, indent=4) + def __save_peerings(self): + with open(self.__peering_file, "w") as p: + json.dump(self.peerings, p, indent=4) + self._amounts = None + + def __generate_wg_conf(self, peering: dict): + return render_template("wireguard.template.conf", peering=peering) + def __generate_bird_conf(self, peering:dict): + return render_template("bgp-peer.template.conf", peering=peering) + + def __install_peering(self, mode: str, peering: dict): + if mode == "add": + wg_conf = self.__generate_wg_conf(peering) + bgp_conf = self.__generate_bird_conf(peering) + with open(f"{self.__config['wg-configs']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as wg_file: + wg_file.write(wg_conf) + + wg_enable = subprocess.run(self.__config["wg-commands"]["enable"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(wg_enable) + wg_up = subprocess.run(self.__config["wg-commands"]["up"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(wg_up) + time.sleep(5) + with open(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as bgp_file: + bgp_file.write(bgp_conf) + bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(bgp_reload) + + return 200 + elif mode == "update": + wg_conf = self.__generate_wg_conf(peering) + bgp_conf = self.__generate_bird_conf(peering) + with open(f"{self.__config['wg-configs']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as wg_file: + wg_file.write(wg_conf) + + wg_down = subprocess.run(self.__config["wg-commands"]["down"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(wg_down) + wg_up = subprocess.run(self.__config["wg-commands"]["up"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(wg_up) + time.sleep(5) + with open(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as bgp_file: + bgp_file.write(bgp_conf) + bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(bgp_reload) + + return 200 + elif mode == "delete": + os.remove(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf") + bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(bgp_reload) + time.sleep(5) + wg_down = subprocess.run(self.__config["wg-commands"]["down"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(wg_down) + wg_disable = subprocess.run(self.__config["wg-commands"]["disable"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) + print(wg_disable) + + return 200 + + return 405 + + def get_peerings_by_asn(self, asn): + if asn in self.peerings: + return self.peerings[asn] + else: + return [] + + def exists(self, asn, MNT=NotSpecified, wg_key=NotSpecified, endpoint=NotSpecified, ipv6ll=NotSpecified, ipv4=NotSpecified, ipv6=NotSpecified, bgp_mp=NotSpecified, bgp_enh=NotSpecified): + """checks if a peerings with specific data already exists""" + # check if mnt is specified, already exists in the database and if that mnt has the specified ASn -> if not: return False + if not asn in self.peerings: + return False + selected_peerings = self.peerings[asn] + # check if the ASn even has peerings + if len(selected_peerings) == 0: + return False + for p in selected_peerings: + if (not wg_key or p["wg_key"] == wg_key): + return True + return False + + def add_peering(self, MNT, ASN, wg_key, node=NotSpecified, endpoint=NotSpecified, ipv6ll=NotSpecified, ipv4=NotSpecified, ipv6=NotSpecified, bgp_mp=NotSpecified, bgp_enh=NotSpecified): + asn = ASN + try: + if not asn in self.peerings: + self.peerings[asn] = [] + except KeyError: + self.peerings[asn] = [] + + for p in self.peerings[asn]: + if p["wg_key"] == wg_key: + return False, 409 + + new_peering = {"MNT": MNT, "ASN": asn, "node": config["nodename"], "wg_key": wg_key, "endpoint": endpoint if endpoint != NotSpecified else None, + "ipv6ll": ipv6ll if ipv6ll != NotSpecified else None, "ipv4": ipv4 if ipv4 != NotSpecified else None, "ipv6": ipv6 if ipv6 != NotSpecified else None, + "bgp_mp": bgp_mp if bgp_mp != NotSpecified else True, "bgp_enh": bgp_enh if bgp_enh != NotSpecified else True} + self.peerings[asn].append(new_peering) + + self.__save_peerings() + ret_code = self.__install_peering(mode="add", peering=new_peering) + if ret_code == 200: + return True, 201 + else: + return False, ret_code + + def update_peering(self, ASN, wg_key, MNT=NotSpecified, node=NotSpecified, endpoint=NotSpecified, ipv6ll=NotSpecified, ipv4=NotSpecified, ipv6=NotSpecified, bgp_mp=NotSpecified, bgp_enh=NotSpecified): + asn = ASN + + try: + if not asn in self.peerings: + return False, 404 + except KeyError: + return False, 404 + + success = False + for pNr in range(len(self.peerings[asn])): + if self.peerings[asn][pNr]["node"] == node: + old_peering = self.peerings[asn][pNr] + new_peering = self.peerings[asn][pNr] = {"MNT": MNT if MNT!=NotSpecified else old_peering["MNT"], "ASN": asn, "node": config["nodename"], "wg_key": wg_key, + "endpoint": endpoint if endpoint!=NotSpecified else old_peering["endpoint"], "ipv6ll": ipv6ll if ipv6ll != NotSpecified else old_peering["ipv6ll"], "ipv4": ipv4 if ipv4 != NotSpecified else old_peering["ipv4"], "ipv6": ipv6 if ipv6 != NotSpecified else old_peering["ipv6"], "bgp_mp": bgp_mp if bgp_mp != NotSpecified else old_peering["bgp_mp"], "bgp_enh": bgp_enh if bgp_enh != NotSpecified else old_peering["bgp_enh"]} + success = True + if not success: + return False, 404 + + self.__save_peerings() + ret_code = self.__install_peering(mode="update", peering=new_peering) + if ret_code == 200: + return True, 200 + else: + return False, ret_code + + def delete_peering(self, ASN, node, wg_key=None): + asn = ASN + if not self.exists(asn=asn, wg_key=wg_key): + return False, 404 + for p in self.peerings[asn]: + if p["node"] == node: + if wg_key and p["wg_key"] != wg_key: + continue + self.peerings[asn].remove(p) + print(self.peerings) + self.__save_peerings() + ret_code = self.__install_peering( + mode="delete", peering=p) + if ret_code == 200: + return True, 201 + else: + return False, ret_code + # if nothing got found (should have been catched by self.exists) + return False, 404 + + +config = Config() +peerings = PeeringManager(config) + + +def check_ACL(): + def wrapper(f): + @wraps(f) + def decorated(*args, **kwargs): + if request.remote_addr in config["ACL"]: + return f(*args, **kwargs) + else: + return Response(response="Unauthorized", status=401) + return decorated + return wrapper + + +class PeeringsRoute(Resource): + + @check_ACL() + def get(self): + parser = reqparse.RequestParser() # initialize + parser.add_argument('ASN', required=True) + parser.add_argument('node', required=True, default=config["nodename"]) + parser.add_argument('wg_key') + args = parser.parse_args() # parse arguments to dictionary + + requested_peerings = peerings.get_peerings_by_asn(args["ASN"]) + if requested_peerings: + return {"success": True, "asn": args["ASN"], "peerings": requested_peerings}, 200 + else: + return {"success": False, "asn": args["ASN"], "error": "not found", "peerings": []}, 404 + + @check_ACL() + def post(self): + # print(request.get_json()) + # {'MNT': 'LARE-MNT', 'ASN': '4242423035', 'node': 'node1', + # 'wg_key': 'uM92Rks/Em2n7QLGek6OUXyGO1P/hEelMQNLlW85J2o=', + # 'endpoint': None, 'ipv6ll': 'fe80::2', 'ipv4': None, 'ipv6': None, + # 'bgp_mp': True, 'bgp_enh': True} + parser = reqparse.RequestParser() # initialize + parser.add_argument('MNT', required=True) + parser.add_argument('ASN', required=True) + parser.add_argument('node', required=True, default=config["nodename"]) + parser.add_argument('wg_key', required=True) + parser.add_argument('endpoint') + parser.add_argument('ipv6ll') + parser.add_argument('ipv4') + parser.add_argument('ipv6') + parser.add_argument('bgp_mp') + parser.add_argument('bgp_enh') + args = parser.parse_args() # parse arguments to dictionary + + if not peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]): + status, code = peerings.add_peering(**args) + if status: + return {"success": True, "new_peering": args}, 201 + else: + pass + else: + code = 409 + + if code == 409: + error_msg = "already exists" + elif code == "50x": + error_msg = "abc" + + return {"success": False, "error": error_msg}, code + + @check_ACL() + def put(self): + + parser = reqparse.RequestParser() # initialize + parser.add_argument('MNT', required=True) + parser.add_argument('ASN', required=True) + parser.add_argument('node', required=True, default=config["nodename"]) + parser.add_argument('wg_key', required=True, nullable=False) + parser.add_argument('endpoint', store_missing=False) + parser.add_argument('ipv6ll', store_missing=False) + parser.add_argument('ipv4', store_missing=False) + parser.add_argument('ipv6', store_missing=False) + parser.add_argument('bgp_mp', store_missing=False) + parser.add_argument('bgp_enh', store_missing=False) + args = parser.parse_args() # parse arguments to dictionary + print(args) + if peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]): + ret = peerings.update_peering(**args) + if ret: + return {"success": True, "new_peering": args}, 200 + else: + return {"success": False, "new_peering": args}, 500 + + else: + return {"success": False, "error": "not found"}, 404 + + @check_ACL() + def delete(self): + parser = reqparse.RequestParser() # initialize + parser.add_argument('ASN', required=True, nullable=False) + parser.add_argument('node', required=True, default=config["nodename"]) + parser.add_argument('wg_key', required=True, nullable=False) + args = parser.parse_args() # parse arguments to dictionary + + if peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]): + print(args) + ret, code = peerings.delete_peering(**args) + if ret: + return {"success": True, "deleted": args} + else: + code = 404 + if code == 404: + error_msg = "not found" + return {"success": False, "error": error_msg}, code + + +api.add_resource(PeeringsRoute, '/peerings/') +app.template_folder = config["templates"] + +def main(): + if "production" in config and config["production"] == False: + logging.getLogger(__name__).setLevel(0) + app.run(host=config["listen"], port=config["port"], + debug=config["debug-mode"], threaded=True) + else: + from waitress import serve + logging.getLogger(__name__).setLevel(logging.INFO) + serve(app, host=config["listen"], port=config["port"]) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/nodes/node.config.sample.jsonc b/nodes/node.config.sample.jsonc new file mode 100644 index 0000000..b6414e1 --- /dev/null +++ b/nodes/node.config.sample.jsonc @@ -0,0 +1,21 @@ +{ + "MNT": "YOUR-MNT", // your MNT tag + "ASN": "424242000", //Your ASN (used to generate default peer ListenPorts) + "listen": "0.0.0.0", + "port": 8142, + "nodename": "node123", + "ACL": ["127.0.0.1"], // address of the server running the webinterface (for ACL); if "listen" is "::"(or other ipv6) and the the server is connecting via ipv4 use"::ffff:" + "peerings": "/path/to/peerings.json", // optional; default "$PWD/peerings.json", file to save existing peerings to + "production": true, //optional, default true; + "debug-mode": false, // optional; whethet to enable debugging; default false + "wg-configs": "/etc/wireguard/", // optional, default: "/etc/wireguard/"; directory where the wireguard configs are located + "wg-commands": { // {MNT} will get replaced with the lowercase mnter without "-MNT" + "enable": "systemctl enable wg-quick@dn42_{MNT}", //command to execute for enabling the wg-interface + "up": "systemctl start wg-quick@dn42_{MNT}", //command to execute for starting the wg-interface + "down": "systemctl stop wg-quick@dn42_{MNT}", //command to execute for stopping the wg-interface + "disable": "systemctl disable wg-quick@dn42_{MNT}" //command to execute for disabling the wg-interface + }, + "bird-peers": "/etc/bird/peers/", // optional, default: "/etc/bird/peers/"; directory where bird peers are to be located + "bird-reload": "birdc configure", // optional, default: "birdc configure"; command to reconfigure bird or other bgp daemon + "templates": "templates" //optional, default "$PWD/templates"; directory where "wireguard.template.conf" and "bgo-peer.template.conf" are located +} \ No newline at end of file diff --git a/nodes/templates/bgp-peer.template.conf b/nodes/templates/bgp-peer.template.conf new file mode 100644 index 0000000..6671f56 --- /dev/null +++ b/nodes/templates/bgp-peer.template.conf @@ -0,0 +1,36 @@ +{% if peering["bgp_mp"] %} +protocol bgp dn42_{{peering["MNT"][:-4].lower()}} from dnpeers { + neighbor {{peering["ipv6ll"]}} as {{peering["ASN"]}}; + interface "dn42_{{peering['MNT'][:-4].lower()}}"; + passive off; + + ipv4 { + {# TODO: implement pinging peer for bgp-communities + #import where dn42_import_filter(x,y,z); + #export where dn42_export_filter(x,y,z); + #} + extended next hop {% if peering["bgp_enh"] %}on{%else%}off{%endif%}; + }; + ipv6 { + {# TODO: implement pinging peer for bgp-communities + #import where dn42_import_filter(x,y,z); + #export where dn42_export_filter(x,y,z); + #} + extended next hop {% if peering["bgp_enh"] %}on{%else%}off{%endif%}; + }; +}; +{%else%} +protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_4 from dnpeers { + neighbor {{peering["ipv4"]}} as {{peering["ASN"]}}; + passive off; + #import where dn42_import_filter(x,y,z); + #export where dn42_export_filter(x,y,z); +}; + +protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_6 from dnpeers { + neighbor {{peering["ipv6"]}} as {{peering["ASN"]}}; + passive off; + #import where dn42_import_filter(x,y,z); + #export where dn42_export_filter(x,y,z); +} +{%endif%} \ No newline at end of file diff --git a/nodes/templates/wireguard.template.conf b/nodes/templates/wireguard.template.conf new file mode 100644 index 0000000..0866dc4 --- /dev/null +++ b/nodes/templates/wireguard.template.conf @@ -0,0 +1,13 @@ +[Interface] +PostUp = wg set %i private-key /etc/wireguard/dn42.priv +ListenPort = 2{{peering["ASN"][-4:]}} +{% if peering["ipv4"] %}PostUp = /sbin/ip addr add dev %i 172.22.125.130/32 peer {{peering["ipv4"]}}/32 +{%endif%}{% if peering["ipv6"] %}PostUp = /sbin/ip addr add dev %i fe63:5d40:47e5::130/128 peer {{peering["ipv6"]}}/128 +{%endif%}{% if peering["ipv6ll"] %}PostUp = /sbin/ip addr add dev %i fe80::3035:130/128 peer {{peering["ipv6ll"]}}/128{%endif%} +Table = off + +# {{peering["MNT"]}} +[Peer] +PublicKey = {{peering["wg_key"]}} +{% if peering["endpoint"] %}Endpoint = {{peering["endpoint"]}}{%endif%} +AllowedIPs = {% if peering["ipv6ll"] %}{{peering["ipv6ll"]}}/128, {%endif%}fd00::/8, 172.20.0.0/14, 10.0.0.0/8 diff --git a/web/backend/config.sample.jsonc b/web/backend/config.sample.jsonc index be8b9f8..e89da6b 100644 --- a/web/backend/config.sample.jsonc +++ b/web/backend/config.sample.jsonc @@ -21,7 +21,7 @@ "port": 8042, "domain": "example.org", // domain to use for kioubit verification service (with protocol) "base-dir": "/", //optional:directury for which it is reachable (if behind some sort of reverse proxy) default "/" - "peerings": "/path/to/peering-config.json", // optional; default "$PWD/peerings", directory to save existing peerings to + "peerings": "/path/to/peerings.json", // optional; default "$PWD/peerings.json", file to save existing peerings to "production": true, //optional, default true; "debug-mode": false, // optional; whethet to enable debugging; default false "flask-secret-key": "", // secret key for session cookies diff --git a/web/backend/peering_manager.py b/web/backend/peering_manager.py index f92a95a..13ce552 100644 --- a/web/backend/peering_manager.py +++ b/web/backend/peering_manager.py @@ -3,6 +3,8 @@ import os import base64 import logging import random +import threading +import requests class NodeCommunicator: @@ -10,6 +12,10 @@ class NodeCommunicator: self.name = name self.__config = config self.__api_addr = config["api-con"] + + def update(self, action:str, updated_peering:dict): + print(requests.api.post(self.__api_addr+"peerings", json=updated_peering).content) + input() class PeeringManager: @@ -24,6 +30,7 @@ class PeeringManager: self._nodes[node] = NodeCommunicator(name=node, config=self.__config["nodes"][node]) self._amounts = None + self._threads = [] def __load_peerings(self): if not os.path.exists(self.__peering_file): @@ -56,12 +63,18 @@ class PeeringManager: json.dump(self.peerings, p, indent=4) self._amounts = None - def _update_nodes(self, mode, peering, new_peering=None): + def _update_nodes(self, action:str, peering, new_peering=None): """mode: "add","update","delete peering: peering to send to node (included in peering) new_peering: if mode=="update" the new peering to update to """ - pass + if peering["node"] in self._nodes: + thread = threading.Thread(target=self._nodes[peering["node"]].update,kwargs={"action":action,"updated_peering":peering if not new_peering else new_peering,}) + thread.start() + self._threads.append(thread) + + else: return False + def _update_amounts(self): __new = {} for asn in self.peerings["asn"]: @@ -154,12 +167,15 @@ class PeeringManager: success = False for pNr in range(len(self.peerings["asn"][asn])): if self.peerings["asn"][asn][pNr]["node"] == node: - self.peerings["asn"][asn][pNr] = {"MNT": mnt, "ASN": asn, "node": node, "wg_key": wg_key, + old_peering = self.peerings["asn"][asn][pNr] + new_peering = self.peerings["asn"][asn][pNr] = {"MNT": mnt, "ASN": asn, "node": node, "wg_key": wg_key, "endpoint": endpoint, "ipv6ll": ipv6ll, "ipv4": ipv4, "ipv6": ipv6, "bgp_mp": bgp_mp, "bgp_enh": bgp_enh} success = True if not success: return False + self._save_peerings() + self._update_nodes("update", old_peering, new_peering=new_peering) return True def delete_peering(self, asn, node, mnt, wg_key=None): diff --git a/web/frontend/static/style.css b/web/frontend/static/style.css index d9b4e74..93831ef 100644 --- a/web/frontend/static/style.css +++ b/web/frontend/static/style.css @@ -65,13 +65,6 @@ footer.flex { /* # index.html */ /* based on https://codepen.io/t_afif/pen/RwQZLYb */ -.progress-bar { - --w: 100px; /* the width*/ - - --background: lightgrey; /* the background color */ - width: var(--w); - margin: 0 10px; -} .progress { -webkit-appearance: none; -moz-appearance: none; @@ -81,6 +74,13 @@ footer.flex { border-radius: 10em; background: var(--background); } +.progress-bar { + --w: 100px; /* the width*/ + + --background: lightgrey; /* the background color */ + width: var(--w); + margin: 0 10px; +} .progress-value[value]{ --color: /* the progress color */