"small changes"

- remove http only in login.html
- extract PeeringManager into separate file
- add "progress meter" for "used" to "available" peerings
This commit is contained in:
lare 2022-12-12 19:40:30 +01:00
parent d6509ef6fe
commit 1fda3c11a4
8 changed files with 307 additions and 166 deletions

19
.vscode/launch.json vendored
View file

@ -1,5 +1,16 @@
{ {
"configurations": [ "configurations": [
{
"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" },
"args": ["run", "--no-debugger"],
"jinja": true,
"justMyCode": true
},
{ {
"name": "Python: Current File", "name": "Python: Current File",
"type": "python", "type": "python",
@ -8,6 +19,14 @@
"cwd": "${workspaceFolder}/web/", "cwd": "${workspaceFolder}/web/",
"console": "integratedTerminal", "console": "integratedTerminal",
"justMyCode": true "justMyCode": true
},
{
"name": "Python: backend",
"type": "python",
"request": "launch",
"program": "backend/main.py",
"cwd": "${workspaceFolder}/web/",
"justMyCode": true
} }
] ]
} }

View file

@ -19,7 +19,7 @@
"ASN": "424242000", //Your ASN (used to generate default peer ListenPorts) "ASN": "424242000", //Your ASN (used to generate default peer ListenPorts)
"listen": "0.0.0.0", "listen": "0.0.0.0",
"port": 8042, "port": 8042,
"domain": "example.org", // domain to use for kioubit verification service "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 "/" "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/peering-config.json", // optional; default "$PWD/peerings", directory to save existing peerings to
"production": true, //optional, default true; "production": true, //optional, default true;

View file

@ -10,6 +10,7 @@ import random
from functools import wraps from functools import wraps
from ipaddress import ip_address, ip_network, IPv4Network, IPv6Network from ipaddress import ip_address, ip_network, IPv4Network, IPv6Network
import kioubit_verify import kioubit_verify
from peering_manager import PeeringManager
app = Flask(__name__) app = Flask(__name__)
@ -22,7 +23,7 @@ class Config (dict):
if os.path.exists("./config.json"): if os.path.exists("./config.json"):
self.configfile = "./config.json" self.configfile = "./config.json"
elif os.path.exists("/etc/dn42-autopeer/config.json"): elif os.path.exists("/etc/dn42-autopeer/config.json"):
self.configfile = "/etc/dn42-autopeer/config,json" self.configfile = "/etc/dn42-autopeer/config.json"
else: else:
raise FileNotFoundError( raise FileNotFoundError(
"no config file found in ./config.json or /etc/dn42-autopeer/config.json") "no config file found in ./config.json or /etc/dn42-autopeer/config.json")
@ -58,152 +59,19 @@ class Config (dict):
if not "peerings-data" in self._config: if not "peerings-data" in self._config:
self._config["peering-data"] = "./peerings" self._config["peering-data"] = "./peerings"
if not "nodes" in self._config:
self,_config = {}
for node in self._config["nodes"]:
if not "capacity" in self._config["nodes"][node]:
self._config["nodes"][node]["capacity"] = -1
logging.info(self._config) logging.info(self._config)
class PeeringManager:
def __init__(self, peerings_file):
self._peering_file = peerings_file
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({"mnter": {}, "asn": {}}, 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({"mnter": {}, "asn": {}}, 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)
def _update_nodes(mode, 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
def exists(self, asn, node, mnt=None, wg_key=None, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
"""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 mnt and not (mnt in self.peerings["mnter"] and asn in self.peerings["mnter"][mnt]):
return False
selected_peerings = self.peerings["asn"][asn]
# check if the ASn even has peerings
if len(selected_peerings) == 0:
return False
for p in selected_peerings:
if p["node"] == node:
if (not wg_key or p["wg_key"] == wg_key) and (not endpoint or p["endpoint"] == endpoint) \
and (not ipv6ll or p["ipv6ll"] == ipv6ll) and (not ipv4 or p["ipv4"] == ipv4) and (not ipv6 or p["ipv6"] == ipv6)\
and (not bgp_mp or p["bgp_mp"] == bgp_mp) and (not bgp_enh or p["bgp_enh"] == bgp_enh):
return True
return False
def get_peerings_by_mnt(self, mnt):
# print(self.peerings)
try:
out = []
for asn in self.peerings["mnter"][mnt]:
try:
for peering in self.peerings["asn"][asn]:
out.append(peering)
except KeyError as e:
pass
return out
except KeyError:
return {}
def add_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
# check if this MNT already has a/this asn
try:
if not asn in self.peerings["mnter"][mnt]:
# ... and add it if it hasn't
self.peerings[mnt].append(asn)
except KeyError:
# ... and cerate it if it doesn't have any yet
self.peerings["mnter"][mnt] = [asn]
try:
if not asn in self.peerings["asn"]:
self.peerings["asn"][asn] = []
except KeyError:
self.peerings["asn"][asn] = []
# deny more than one peering per ASN to one node
for peering in self.peerings["asn"][asn]:
if peering["node"] == node:
return False
self.peerings["asn"][asn].append({"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})
self._save_peerings()
return True
def update_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
# check if this MNT already has a/this asn
try:
if not asn in self.peerings["mnter"][mnt]:
# ... and add it if it hasn't
self.peerings[mnt].append(asn)
except KeyError:
# ... and cerate it if it doesn't have any yet
self.peerings["mnter"][mnt] = [asn]
try:
if not asn in self.peerings["asn"]:
return False
except KeyError:
return False
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,
"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()
return True
def delete_peering(self, asn, node, mnt, wg_key=None):
if not self.exists(asn, node, mnt=mnt, wg_key=wg_key):
return False
for p in self.peerings["asn"][asn]:
if p["node"] == node:
if wg_key and p["wg_key"] != wg_key:
continue
self.peerings["asn"][asn].remove(p)
self._save_peerings()
return True
# if nothing got found (should have been catched by self.exists)
return False
config = Config() config = Config()
peerings = PeeringManager(config["peerings"]) peerings = PeeringManager(config)
kverifyer = kioubit_verify.AuthVerifyer(config["domain"]) kverifyer = kioubit_verify.AuthVerifyer(config["domain"])
@ -241,9 +109,9 @@ def check_peering_data(form):
else: else:
new_peering["peer-v6"] = None new_peering["peer-v6"] = None
new_peering["bgp-mp"] = form["bgp-multi-protocol"] if "bgp-multi-protocol" in form else "off" new_peering["bgp-mp"] = form["bgp-multi-protocol"] if "bgp-multi-protocol" in form else "off"
new_peering["bgp-mp"] = True if new_peering["bgp-mp"] else False new_peering["bgp-mp"] = True if new_peering["bgp-mp"] == "on" else False
new_peering["bgp-enh"] = form["bgp-extended-next-hop"] if "bgp-extended-next-hop" in form else "off" new_peering["bgp-enh"] = form["bgp-extended-next-hop"] if "bgp-extended-next-hop" in form else "off"
new_peering["bgp-enh"] = True if new_peering["bgp-enh"] else False new_peering["bgp-enh"] = True if new_peering["bgp-enh"] == "on" else False
#new_peering[""] = form["peer-wgkey"] #new_peering[""] = form["peer-wgkey"]
except ValueError as e: except ValueError as e:
print(f"error: {e.args}") print(f"error: {e.args}")
@ -322,13 +190,13 @@ def check_peering_data(form):
# check bgp options # check bgp options
try: try:
if new_peering["bgp-mp"] == "off" and new_peering["bgp-enh"] == "on": if new_peering["bgp-mp"] == False and new_peering["bgp-enh"] == True:
return False, "extended next hop requires multiprotocol bgp" return False, "extended next hop requires multiprotocol bgp"
if new_peering["bgp-mp"] == "off": if new_peering["bgp-mp"] == False:
if not (new_peering["peer-v4"] and (new_peering["peer-v6"] or new_peering["peer-v6ll"])): if not (new_peering["peer-v4"] and (new_peering["peer-v6"] or new_peering["peer-v6ll"])):
return False, "ipv4 and ipv6 addresses required when not having MP-BGP" return False, "ipv4 and ipv6 addresses required when not having MP-BGP"
except ValueError: except ValueError:
... pass
return True, new_peering return True, new_peering
@ -537,13 +405,13 @@ def index():
# print(config._config["nodes"]) # print(config._config["nodes"])
# for node in config["nodes"].values(): # for node in config["nodes"].values():
# print (node) # print (node)
return render_template("index.html", session=session, config=config._config) return render_template("index.html", session=session, config=config, peerings = peerings)
app.static_folder = config["flask-template-dir"]+"/static/"
app.template_folder = config["flask-template-dir"]
app.secret_key = config["flask-secret-key"]
def main(): def main():
app.static_folder = config["flask-template-dir"]+"/static/"
app.template_folder = config["flask-template-dir"]
app.secret_key = config["flask-secret-key"]
if "production" in config and config["production"] == False: if "production" in config and config["production"] == False:
logging.getLogger(__name__).setLevel(0) logging.getLogger(__name__).setLevel(0)
app.run(host=config["listen"], port=config["port"], app.run(host=config["listen"], port=config["port"],

View file

@ -0,0 +1,176 @@
import json
import os
import base64
import logging
import random
class NodeCommunicator:
def __init__(self, name:str, config):
self.name = name
self.__config = config
self.__api_addr = config["api-con"]
class PeeringManager:
def __init__(self, config):
self.__config = config
self.__peering_file = config["peerings"]
self.__load_peerings()
self._nodes = {}
for node in self.__config["nodes"]:
self._nodes[node] = NodeCommunicator(name=node, config=self.__config["nodes"][node])
self._amounts = None
def __load_peerings(self):
if not os.path.exists(self.__peering_file):
with open(self.__peering_file, "x") as p:
json.dump({"mnter": {}, "asn": {}}, 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({"mnter": {}, "asn": {}}, 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 _update_nodes(self, mode, 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
def _update_amounts(self):
__new = {}
for asn in self.peerings["asn"]:
for peering in self.peerings["asn"][asn]:
if not peering["node"] in __new:
__new[peering["node"]] = 0
__new[peering["node"]] += 1
self._amounts = __new
def amount_by_node(self, node_name: str):
if self._amounts ==None:
self._update_amounts()
try:
return self._amounts[node_name]
except KeyError:
return 0
def exists(self, asn, node, mnt=None, wg_key=None, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
"""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 mnt and not (mnt in self.peerings["mnter"] and asn in self.peerings["mnter"][mnt]):
return False
selected_peerings = self.peerings["asn"][asn]
# check if the ASn even has peerings
if len(selected_peerings) == 0:
return False
for p in selected_peerings:
if p["node"] == node:
if (not wg_key or p["wg_key"] == wg_key) and (not endpoint or p["endpoint"] == endpoint) \
and (not ipv6ll or p["ipv6ll"] == ipv6ll) and (not ipv4 or p["ipv4"] == ipv4) and (not ipv6 or p["ipv6"] == ipv6)\
and (not bgp_mp or p["bgp_mp"] == bgp_mp) and (not bgp_enh or p["bgp_enh"] == bgp_enh):
return True
return False
def get_peerings_by_mnt(self, mnt):
# print(self.peerings)
try:
out = []
for asn in self.peerings["mnter"][mnt]:
try:
for peering in self.peerings["asn"][asn]:
out.append(peering)
except KeyError as e:
pass
return out
except KeyError:
return {}
def add_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
# check if this MNT already has a/this asn
try:
if not asn in self.peerings["mnter"][mnt]:
# ... and add it if it hasn't
self.peerings[mnt].append(asn)
except KeyError:
# ... and cerate it if it doesn't have any yet
self.peerings["mnter"][mnt] = [asn]
try:
if not asn in self.peerings["asn"]:
self.peerings["asn"][asn] = []
except KeyError:
self.peerings["asn"][asn] = []
# deny more than one peering per ASN to one node
for peering in self.peerings["asn"][asn]:
if peering["node"] == node:
return False
self.peerings["asn"][asn].append({"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})
self._save_peerings()
return True
def update_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
# check if this MNT already has a/this asn
try:
if not asn in self.peerings["mnter"][mnt]:
# ... and add it if it hasn't
self.peerings[mnt].append(asn)
except KeyError:
# ... and cerate it if it doesn't have any yet
self.peerings["mnter"][mnt] = [asn]
try:
if not asn in self.peerings["asn"]:
return False
except KeyError:
return False
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,
"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()
return True
def delete_peering(self, asn, node, mnt, wg_key=None):
if not self.exists(asn, node, mnt=mnt, wg_key=wg_key):
return False
for p in self.peerings["asn"][asn]:
if p["node"] == node:
if wg_key and p["wg_key"] != wg_key:
continue
self.peerings["asn"][asn].remove(p)
self._save_peerings()
return True
# if nothing got found (should have been catched by self.exists)
return False

View file

@ -16,7 +16,17 @@
<td>{{node}}</td> <td>{{node}}</td>
<td>{{config["nodes"][node]["country"]}}</td> <td>{{config["nodes"][node]["country"]}}</td>
<td>{{config["nodes"][node]["city"]}}</td> <td>{{config["nodes"][node]["city"]}}</td>
<td></td> <td>
{% set peerings_percentage =
(peerings.amount_by_node(node)/config['nodes'][node]['capacity']*100) %}
<div class="progress progress-bar" title="{{peerings.amount_by_node(node)}}/{{config['nodes'][node]['capacity']}}">
<div
class="progress progress-value"
style="width:'{{peerings_percentage}}px'; background-color: {% if (peerings_percentage<80) %}green{% elif (peerings_percentage < 90) %}orange{%else%}red{%endif%};"
>
</div>
</div>
</td>
<td><a href="peerings/new?node={{node}}">peer</a></td> <td><a href="peerings/new?node={{node}}">peer</a></td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -11,7 +11,7 @@
type="hidden" type="hidden"
id="return" id="return"
name="return" name="return"
value='{{"http://"+config["domain"]+"/api/auth/kverify"}}' value='{{config["domain"]+"/api/auth/kverify"}}'
/> />
<button <button
type="submit" type="submit"

View file

@ -214,13 +214,13 @@
</tr> </tr>
<tr> <tr>
<td><label for="peer-v4">your ipv4</label></td> <td><label for="peer-v4">your ipv4</label></td>
<td><input type="checkbox" name="peer-v4-enabled" id="peer-v4-enabled"onchange="return update_from_v4()" {% if selected_peering["ipv4"] %} checked {% endif %}></td> <td><input type="checkbox" name="peer-v4-enabled" id="peer-v4-enabled" onchange="return update_from_v4()" {% if selected_peering["ipv4"] %} checked {% endif %}></td>
<td><input type="text" name="peer-v4" id="peer-v4"onchange="return update_from_v4()" {% if selected_peering["ipv4"] %}value="{{selected_peering['ipv4']}}" {% endif %}></td> <td><input type="text" name="peer-v4" id="peer-v4" onchange="return update_from_v4()" {% if selected_peering["ipv4"] %}value="{{selected_peering['ipv4']}}" {% endif %}></td>
</tr> </tr>
<tr> <tr>
<td><label for="peer-v6">your ipv6</label></td> <td><label for="peer-v6">your ipv6</label></td>
<td><input type="checkbox" name="peer-v6-enabled" id="peer-v6-enabled"onchange="return update_from_v6()"{% if selected_peering["ipv6"] %} checked {% endif %}></td> <td><input type="checkbox" name="peer-v6-enabled" id="peer-v6-enabled" onchange="return update_from_v6()"{% if selected_peering["ipv6"] %} checked {% endif %}></td>
<td><input type="text" name="peer-v6" id="peer-v6"onchange="return update_from_v6()" {% if selected_peering["ipv6"] %}value="{{selected_peering['ipv6']}}" {% endif %}></td> <td><input type="text" name="peer-v6" id="peer-v6" onchange="return update_from_v6()" {% if selected_peering["ipv6"] %}value="{{selected_peering['ipv6']}}" {% endif %}></td>
</tr> </tr>
<tr> <tr>
<td><h4>BGP</h4></td> <td><h4>BGP</h4></td>
@ -228,13 +228,13 @@
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>MultiProtocol</td> <td><label for="bgp-multi-protocol">MultiProtocol</label></td>
<td><input type="checkbox" name="bgp-multi-protocol" id="bgp-multi-protocol" onchange="return update_from_mpbgp()" {% if selected_peering["bgp-mp"] %} checked {% endif %}></td> <td><input type="checkbox" name="bgp-multi-protocol" id="bgp-multi-protocol" onchange="return update_from_mpbgp()" {% if selected_peering["bgp_mp"] == True %} checked {% endif %}></td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>extended next hop</td> <td><label for="bgp-extended-next-hop">extended next hop</label></td>
<td><input type="checkbox" name="bgp-extended-next-hop" id="bgp-extended-next-hop" onchange="return update_from_enh()" {% if selected_peering["bgp-enh"] %} checked {% endif %}></td> <td><input type="checkbox" name="bgp-extended-next-hop" id="bgp-extended-next-hop" onchange="return update_from_enh()" {% if selected_peering["bgp_enh"] == True%} checked {% endif %}></td>
<td></td> <td></td>
</tr> </tr>
</table> </table>

View file

@ -62,6 +62,72 @@ footer.flex {
flex-direction: row; flex-direction: row;
} }
/* # 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;
appearance: none;
border: none;
height: 10px;
border-radius: 10em;
background: var(--background);
}
.progress-value[value]{
--color: /* the progress color */
/* linear-gradient(#fff8,#fff0), */
/* repeating-linear-gradient(135deg,#0003 0 10px,#0000 0 20px), */
/* if < 30% "red" */
linear-gradient(green 0 0) 0 /calc(var(--w)*.8 - 100%) 1px,
/* if < 60% "orange" */
linear-gradient(orange 0 0) 0 /calc(var(--w)*.9 - 100%) 1px,
/* else "green" */
red;
background-color: var(--color);
align-self: left;
margin: 0;
}
.progress[value]::-webkit-progress-bar {
border-radius: 10em;
background: var(--background);
}
.progress[value]::-webkit-progress-value {
border-radius: 10em;
background: var(--color);
}
.progress[value]::-moz-progress-bar {
border-radius: 10em;
background-color: var(--color);
}
/* .progress:before { */
/* position:absolute; */
/* height:30px; */
/* background:green; */
/* content:'50%'; hrere you should add the text */
/* top:0; */
/* left:0; */
/* width:50%; */
/* display:flex; */
/* color:white; */
/* align-items:center; */
/* justify-content:flex-end; */
/* padding-right:10px; */
/* } */
/* for peerings-{edit,new}.html */
.example-config { .example-config {
background-color: white; background-color: white;
padding: 1%; padding: 1%;
@ -78,6 +144,7 @@ form>div>* {
margin: 10px; margin: 10px;
} }
/* for peerings.html */
.peering { .peering {
border-color: var(--other-background); border-color: var(--other-background);
border-radius: 10px; border-radius: 10px;
@ -99,6 +166,11 @@ form>div>* {
padding: 10px; padding: 10px;
} }
.peering>* {
width: auto;
align-items: center;
}
/* button def */
button, button,
input[type=button] { input[type=button] {
background-color: #00000020; background-color: #00000020;
@ -113,10 +185,6 @@ input[type=button].button-selected {
color: var(--text-color-dark); color: var(--text-color-dark);
} }
.peering>* {
width: auto;
align-items: center;
}
.button-blue { .button-blue {
border-color: lightblue; border-color: lightblue;