Eerste commit
This commit is contained in:
parent
534389b6f4
commit
9928321826
22 changed files with 1594 additions and 0 deletions
4
.env
Normal file
4
.env
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
API_URL=your-vyos-api-url-without-/show
|
||||||
|
API_KEY=your-vyos-api-key
|
||||||
|
|
||||||
|
BGP_TOOLS_IMAGE=bgp-tools-image
|
||||||
16
README.md
Normal file
16
README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# AS215085.net Vyos tools
|
||||||
|
|
||||||
|
### Vyos config
|
||||||
|
1. Login to Vyos and enter configure mode.
|
||||||
|
2. ```set service https api keys id api key 'YourKey'```
|
||||||
|
3. ```set service https listen-address 'VyosIP'```
|
||||||
|
4. ```set service https port '80'```
|
||||||
|
5. ```set service https allow-client address '0.0.0.0/0'```
|
||||||
|
|
||||||
|
### App config
|
||||||
|
1. Edit .env and fill in your vyos api url and api key.
|
||||||
|
2. ```apt install -y python3 python3-jinja2 python3-flask python3-gunicorn```
|
||||||
|
3. ```gunicorn -w 4 -b 0.0.0.0:5000 app:app```
|
||||||
|
|
||||||
|
### License
|
||||||
|
```Apache License, Version 2.0 and the Commons Clause Restriction```
|
||||||
83
app.py
Normal file
83
app.py
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
from jinja2 import Template
|
||||||
|
from flask import Flask, render_template_string, jsonify, url_for, redirect, render_template, request
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
from modules.parse import run_bgp_curl_command, run_arp_curl_command, run_neighbors_curl_command, run_interfaces_curl_command, run_bgp_route_curl_command
|
||||||
|
|
||||||
|
from modules.bgp import parse_bgp_data, generate_bgp_json
|
||||||
|
|
||||||
|
from modules.arp import parse_arp_data, generate_arp_json
|
||||||
|
|
||||||
|
from modules.neighbors import parse_neighbors_data, generate_neighbors_json
|
||||||
|
|
||||||
|
from modules.interfaces import parse_interface_data
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
bgp_image = os.getenv("BGP_TOOLS_IMAGE")
|
||||||
|
return render_template('index.html', bgp_image=bgp_image)
|
||||||
|
|
||||||
|
@app.route("/bgp")
|
||||||
|
def bgp():
|
||||||
|
bgp_data = run_bgp_curl_command()
|
||||||
|
ipv4_info, ipv4_peers, ipv6_info, ipv6_peers = parse_bgp_data(bgp_data)
|
||||||
|
return render_template('bgp.html')
|
||||||
|
|
||||||
|
@app.route("/bgp/json")
|
||||||
|
def bgp_json():
|
||||||
|
bgp_data = run_bgp_curl_command()
|
||||||
|
ipv4_info, ipv4_peers, ipv6_info, ipv6_peers = parse_bgp_data(bgp_data)
|
||||||
|
return jsonify(generate_bgp_json(ipv4_info, ipv4_peers, ipv6_info, ipv6_peers))
|
||||||
|
|
||||||
|
@app.route("/arp")
|
||||||
|
def arp():
|
||||||
|
return render_template("arp.html")
|
||||||
|
|
||||||
|
@app.route("/arp/json")
|
||||||
|
def arp_json():
|
||||||
|
arp_data = run_arp_curl_command()
|
||||||
|
arp_table = parse_arp_data(arp_data)
|
||||||
|
return jsonify(generate_arp_json(arp_table))
|
||||||
|
|
||||||
|
@app.route("/neighbors")
|
||||||
|
def neighbors():
|
||||||
|
return render_template("neighbors.html")
|
||||||
|
|
||||||
|
@app.route("/neighbors/json")
|
||||||
|
def neighborsp_json():
|
||||||
|
neighbors_data = run_neighbors_curl_command()
|
||||||
|
neighbors_table = parse_neighbors_data(neighbors_data)
|
||||||
|
return jsonify(generate_neighbors_json(neighbors_table))
|
||||||
|
|
||||||
|
@app.route('/interfaces')
|
||||||
|
def interface_table_page():
|
||||||
|
return render_template("interfaces.html")
|
||||||
|
|
||||||
|
@app.route('/interfaces/json')
|
||||||
|
def interface_table_summary_json():
|
||||||
|
data = run_interfaces_curl_command()
|
||||||
|
interface_table = parse_interface_data(data)
|
||||||
|
return jsonify({"interface_table": interface_table})
|
||||||
|
|
||||||
|
@app.route('/bgp-route')
|
||||||
|
def bgp_route_page():
|
||||||
|
return render_template("bgp-route.html")
|
||||||
|
|
||||||
|
@app.route('/bgp-route/lookup', methods=['POST'])
|
||||||
|
def bgp_route_lookup():
|
||||||
|
data = request.json
|
||||||
|
ip_version = data.get('ip_version')
|
||||||
|
bgprouteprefix = data.get('bgprouteprefix')
|
||||||
|
|
||||||
|
if not ip_version or not bgprouteprefix:
|
||||||
|
return jsonify({"error": "ip_version and bgprouteprefix are required"}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = run_bgp_route_curl_command(ip_version, bgprouteprefix)
|
||||||
|
return jsonify(result)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
0
modules/__init__.py
Normal file
0
modules/__init__.py
Normal file
21
modules/arp.py
Normal file
21
modules/arp.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
def parse_arp_data(data):
|
||||||
|
arp_table = []
|
||||||
|
|
||||||
|
if "data" in data:
|
||||||
|
raw_data = data["data"]
|
||||||
|
|
||||||
|
for line in raw_data.split("\n"):
|
||||||
|
if line.strip() and not line.startswith("Address") and "---" not in line:
|
||||||
|
arp_info = line.split()
|
||||||
|
if len(arp_info) >= 4:
|
||||||
|
arp_table.append({
|
||||||
|
"address": arp_info[0],
|
||||||
|
"interface": arp_info[1],
|
||||||
|
"link_layer_address": arp_info[2],
|
||||||
|
"state": arp_info[3]
|
||||||
|
})
|
||||||
|
|
||||||
|
return arp_table
|
||||||
|
|
||||||
|
def generate_arp_json(arp_table):
|
||||||
|
return {"arp_table": arp_table}
|
||||||
84
modules/bgp.py
Normal file
84
modules/bgp.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
def parse_bgp_data(data):
|
||||||
|
ipv4_section = ""
|
||||||
|
ipv6_section = ""
|
||||||
|
ipv4_info = {}
|
||||||
|
ipv6_info = {}
|
||||||
|
|
||||||
|
if "data" in data:
|
||||||
|
raw_data = data["data"]
|
||||||
|
|
||||||
|
ipv4_marker = "IPv4 Unicast Summary (VRF bgp):"
|
||||||
|
ipv6_marker = "IPv6 Unicast Summary (VRF bgp):"
|
||||||
|
|
||||||
|
ipv4_start = raw_data.find(ipv4_marker)
|
||||||
|
ipv6_start = raw_data.find(ipv6_marker)
|
||||||
|
|
||||||
|
if ipv4_start != -1:
|
||||||
|
if ipv6_start != -1:
|
||||||
|
ipv4_section = raw_data[ipv4_start + len(ipv4_marker):ipv6_start].strip()
|
||||||
|
else:
|
||||||
|
ipv4_section = raw_data[ipv4_start + len(ipv4_marker):].strip()
|
||||||
|
ipv4_info = extract_bgp_info(ipv4_section)
|
||||||
|
|
||||||
|
if ipv6_start != -1:
|
||||||
|
ipv6_section = raw_data[ipv6_start + len(ipv6_marker):].strip()
|
||||||
|
ipv6_info = extract_bgp_info(ipv6_section)
|
||||||
|
|
||||||
|
def process_peers(peer_data):
|
||||||
|
peers = []
|
||||||
|
for line in peer_data.split("\n"):
|
||||||
|
if line.strip().startswith("Neighbor"):
|
||||||
|
continue
|
||||||
|
if line.strip():
|
||||||
|
peer_info = line.split()
|
||||||
|
if len(peer_info) >= 12:
|
||||||
|
peers.append({
|
||||||
|
"neighbor": peer_info[0],
|
||||||
|
"version": peer_info[1],
|
||||||
|
"as_number": peer_info[2],
|
||||||
|
"msg_received": peer_info[3],
|
||||||
|
"msg_sent": peer_info[4],
|
||||||
|
"table_version": peer_info[5],
|
||||||
|
"in_queue": peer_info[6],
|
||||||
|
"out_queue": peer_info[7],
|
||||||
|
"up_down": peer_info[8],
|
||||||
|
"state_pfx_rcd": peer_info[9],
|
||||||
|
"prefix_sent": peer_info[10],
|
||||||
|
"description": " ".join(peer_info[11:])
|
||||||
|
})
|
||||||
|
return peers
|
||||||
|
|
||||||
|
ipv4_peers = process_peers(ipv4_section)
|
||||||
|
ipv6_peers = process_peers(ipv6_section)
|
||||||
|
|
||||||
|
return ipv4_info, ipv4_peers, ipv6_info, ipv6_peers
|
||||||
|
|
||||||
|
def extract_bgp_info(raw_data):
|
||||||
|
lines = raw_data.split("\n")
|
||||||
|
info = {}
|
||||||
|
for line in lines:
|
||||||
|
if "BGP router identifier" in line:
|
||||||
|
parts = line.split(",")
|
||||||
|
info["router_id"] = parts[0].split("identifier")[1].strip()
|
||||||
|
info["local_as"] = parts[1].split("number")[1].strip().split(" ")[0]
|
||||||
|
if "vrf-id" in parts[-1]:
|
||||||
|
info["vrf_id"] = parts[-1].split("vrf-id")[1].strip()
|
||||||
|
if "BGP table version" in line:
|
||||||
|
info["table_version"] = line.split("version")[1].strip()
|
||||||
|
if "RIB entries" in line:
|
||||||
|
parts = line.split(",")
|
||||||
|
info["rib_entries"] = parts[0].split("entries")[1].strip()
|
||||||
|
info["rib_memory"] = parts[1].split("using")[1].strip()
|
||||||
|
if "Peers" in line:
|
||||||
|
parts = line.split(",")
|
||||||
|
info["peers"] = parts[0].split("Peers")[1].strip()
|
||||||
|
info["peers_memory"] = parts[1].split("using")[1].strip()
|
||||||
|
return info
|
||||||
|
|
||||||
|
def generate_bgp_json(ipv4_info, ipv4_peers, ipv6_info, ipv6_peers):
|
||||||
|
return {
|
||||||
|
"ipv4_info": ipv4_info,
|
||||||
|
"ipv4_peers": ipv4_peers,
|
||||||
|
"ipv6_info": ipv6_info,
|
||||||
|
"ipv6_peers": ipv6_peers
|
||||||
|
}
|
||||||
30
modules/interfaces.py
Normal file
30
modules/interfaces.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
def parse_interface_data(data):
|
||||||
|
interface_table = []
|
||||||
|
|
||||||
|
if "data" in data:
|
||||||
|
raw_data = data["data"]
|
||||||
|
|
||||||
|
for line in raw_data.split("\n"):
|
||||||
|
if line.startswith("Interface") and "IP Address" in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith("Codes:"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.strip().startswith('-'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.strip():
|
||||||
|
interface_info = line.split()
|
||||||
|
if len(interface_info) >= 6:
|
||||||
|
interface_table.append({
|
||||||
|
"interface": interface_info[0],
|
||||||
|
"ip_address": interface_info[1] if interface_info[1] != '-' else 'N/A',
|
||||||
|
"mac_address": interface_info[2],
|
||||||
|
"vrf": interface_info[3],
|
||||||
|
"mtu": interface_info[4],
|
||||||
|
"status": interface_info[5],
|
||||||
|
"description": " ".join(interface_info[6:])
|
||||||
|
})
|
||||||
|
|
||||||
|
return interface_table
|
||||||
21
modules/neighbors.py
Normal file
21
modules/neighbors.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
def parse_neighbors_data(data):
|
||||||
|
neighbors_table = []
|
||||||
|
|
||||||
|
if "data" in data:
|
||||||
|
raw_data = data["data"]
|
||||||
|
|
||||||
|
for line in raw_data.split("\n"):
|
||||||
|
if line.strip() and not line.startswith("Address") and "---" not in line:
|
||||||
|
neighbors_info = line.split()
|
||||||
|
if len(neighbors_info) >= 4:
|
||||||
|
neighbors_table.append({
|
||||||
|
"address": neighbors_info[0],
|
||||||
|
"interface": neighbors_info[1],
|
||||||
|
"link_layer_address": neighbors_info[2],
|
||||||
|
"state": neighbors_info[3]
|
||||||
|
})
|
||||||
|
|
||||||
|
return neighbors_table
|
||||||
|
|
||||||
|
def generate_neighbors_json(neighbors_table):
|
||||||
|
return {"neighbors_table": neighbors_table}
|
||||||
58
modules/parse.py
Normal file
58
modules/parse.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
API_URL = os.getenv("API_URL")
|
||||||
|
API_KEY = os.getenv("API_KEY")
|
||||||
|
|
||||||
|
def run_bgp_curl_command():
|
||||||
|
curl_command = [
|
||||||
|
"curl", "-k", "--location", "--request", "POST", f"{API_URL}/show",
|
||||||
|
"--form", "data={\"op\": \"show\", \"path\": [\"bgp\", \"vrf\", \"bgp\", \"summ\"]}",
|
||||||
|
"--form", f"key={API_KEY}"
|
||||||
|
]
|
||||||
|
response = subprocess.check_output(curl_command, text=True)
|
||||||
|
return json.loads(response)
|
||||||
|
|
||||||
|
def run_arp_curl_command():
|
||||||
|
curl_command = [
|
||||||
|
"curl", "-k", "--location", "--request", "POST", f"{API_URL}/show",
|
||||||
|
"--form", "data={\"op\": \"show\", \"path\": [\"arp\"]}",
|
||||||
|
"--form", f"key={API_KEY}"
|
||||||
|
]
|
||||||
|
response = subprocess.check_output(curl_command, text=True)
|
||||||
|
return json.loads(response)
|
||||||
|
|
||||||
|
def run_neighbors_curl_command():
|
||||||
|
curl_command = [
|
||||||
|
"curl", "-k", "--location", "--request", "POST", f"{API_URL}/show",
|
||||||
|
"--form", "data={\"op\": \"show\", \"path\": [\"ipv6\", \"neighbors\"]}",
|
||||||
|
"--form", f"key={API_KEY}"
|
||||||
|
]
|
||||||
|
response = subprocess.check_output(curl_command, text=True)
|
||||||
|
return json.loads(response)
|
||||||
|
|
||||||
|
def run_interfaces_curl_command():
|
||||||
|
curl_command = [
|
||||||
|
"curl", "-k", "--location", "--request", "POST", f"{API_URL}/show",
|
||||||
|
"--form", "data={\"op\": \"show\", \"path\": [\"interfaces\"]}",
|
||||||
|
"--form", f"key={API_KEY}"
|
||||||
|
]
|
||||||
|
response = subprocess.check_output(curl_command, text=True)
|
||||||
|
return json.loads(response)
|
||||||
|
|
||||||
|
def run_bgp_route_curl_command(ip_version, bgprouteprefix):
|
||||||
|
data_json = {
|
||||||
|
"op": "show",
|
||||||
|
"path": ["bgp", "vrf", "bgp", ip_version, bgprouteprefix]
|
||||||
|
}
|
||||||
|
curl_command = [
|
||||||
|
"curl", "-k", "--location", "--request", "POST", f"{API_URL}/show",
|
||||||
|
"--form", f"data={json.dumps(data_json)}",
|
||||||
|
"--form", f"key={API_KEY}"
|
||||||
|
]
|
||||||
|
response = subprocess.check_output(curl_command, text=True)
|
||||||
|
return json.loads(response)
|
||||||
6
static/css/bootstrap.min.css
vendored
Normal file
6
static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
170
static/css/style.css
Normal file
170
static/css/style.css
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
position: relative;
|
||||||
|
background: url('../img/extern-bureaublad-1024x638-1.webp') no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
color: white;
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1, header p {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2; /* hoger dan de overlay */
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1440px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
section h2 {
|
||||||
|
color: #07AAF9;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: #2D2E43;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamcon {
|
||||||
|
border: 2px solid #07AAF9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
width: 45%;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #dddddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
th.sortable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-arrow::after {
|
||||||
|
content: "⇅";
|
||||||
|
margin-left: 5px;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
th.asc .sort-arrow::after {
|
||||||
|
content: "▲";
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
th.desc .sort-arrow::after {
|
||||||
|
content: "▼";
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
margin-top: 6rem;
|
||||||
|
margin-bottom: 6rem;
|
||||||
|
border-width: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
margin-top: 6rem;
|
||||||
|
margin-bottom: 6rem;
|
||||||
|
border-width: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#refreshSpinner.spinner-border,
|
||||||
|
#refreshSpinner2.spinner-border {
|
||||||
|
width: 0.84rem;
|
||||||
|
height: 0.84rem;
|
||||||
|
margin-top: 0rem;
|
||||||
|
margin-bottom: 0rem;
|
||||||
|
border-width: 0.15em;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bgpOutput {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
min-height: 400px;
|
||||||
|
min-width: 800px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: Consolas, monospace, monospace;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
.asn-highlight {
|
||||||
|
color: #e67e22;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.asn215085-highlight {
|
||||||
|
color: #07AAF9;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
strong.best-line {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
BIN
static/img/as215085-logo.png
Normal file
BIN
static/img/as215085-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
BIN
static/img/cropped-Pixelhosting-logo-favicon-32x32.png
Normal file
BIN
static/img/cropped-Pixelhosting-logo-favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
static/img/extern-bureaublad-1024x638-1.webp
Normal file
BIN
static/img/extern-bureaublad-1024x638-1.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
static/js/materialize.min.js
vendored
Normal file
6
static/js/materialize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
187
templates/arp.html
Normal file
187
templates/arp.html
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AS215085 - Router tools | ARP table</title>
|
||||||
|
<link rel="icon" type="image/png" href="../static/img/cropped-Pixelhosting-logo-favicon-32x32.png">
|
||||||
|
<script src="../static/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../static/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-content">
|
||||||
|
<a href="/"> <img src="../static/img/as215085-logo.png" alt="AS215085 Logo" style="height: 120px; opacity: 1;!important"> </a>
|
||||||
|
<h2><b>core1.doet.pixelhosting.nl</b></h2>
|
||||||
|
<p><b>Proudly delivering the backbone for PixelHosting’s services</b></p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #07AAF9;">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav justify-content-center w-100">
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp">BGP summary</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp-route">BGP route</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/arp">ARP table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/neighbors">Neighbors table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/interfaces">Interfaces table</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<section id="arp">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" id="arpSearch" class="form-control" placeholder="Search in ARP table..." onkeyup="filterTable('arpSearch', 'arpTable')">
|
||||||
|
<button id="refreshButton" class="btn btn-outline-primary" type="button" onclick="refreshArpTable()">
|
||||||
|
<span id="refreshIcon">⟳</span>
|
||||||
|
<span id="refreshSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
Refresh data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table class="striped table table-bordered" id="arpTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="sortable" onclick="sortTable('arpTable', 0, this)">Address <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('arpTable', 1, this)">Interface <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('arpTable', 2, this)">Link Layer Address <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('arpTable', 3, this)">State <span class="sort-arrow"></span></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="arpTableBody">
|
||||||
|
<tr id="arp-loading-row">
|
||||||
|
<td colspan="4" class="text-center">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><a href="/arp/json">JSON version</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© 2020 – <span id="year"></span> AS215085 (PixelHosting). All rights reserved.</p>
|
||||||
|
<script> document.getElementById("year").textContent = new Date().getFullYear(); </script>
|
||||||
|
</footer>
|
||||||
|
<script src="../static/js/materialize.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function filterTable(searchInputId, tableId) {
|
||||||
|
const input = document.getElementById(searchInputId);
|
||||||
|
const filter = input.value.toUpperCase();
|
||||||
|
const table = document.getElementById(tableId);
|
||||||
|
const tr = table.getElementsByTagName("tr");
|
||||||
|
|
||||||
|
for (let i = 1; i < tr.length; i++) {
|
||||||
|
tr[i].style.display = "none";
|
||||||
|
const td = tr[i].getElementsByTagName("td");
|
||||||
|
for (let j = 0; j < td.length; j++) {
|
||||||
|
if (td[j]) {
|
||||||
|
const txtValue = td[j].textContent || td[j].innerText;
|
||||||
|
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||||
|
tr[i].style.display = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTable(tableId, columnIndex, th) {
|
||||||
|
const table = document.getElementById(tableId);
|
||||||
|
const tbody = table.tBodies[0];
|
||||||
|
const rows = Array.from(tbody.rows);
|
||||||
|
const headers = Array.from(th.parentNode.children);
|
||||||
|
|
||||||
|
headers.forEach(header => {
|
||||||
|
if (header !== th) {
|
||||||
|
header.classList.remove('asc', 'desc');
|
||||||
|
header.setAttribute('data-sort-state', 'none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentState = th.getAttribute('data-sort-state') || 'none';
|
||||||
|
let newState = currentState === 'none' ? 'asc' : currentState === 'asc' ? 'desc' : 'none';
|
||||||
|
|
||||||
|
th.classList.remove('asc', 'desc');
|
||||||
|
if (newState !== 'none') th.classList.add(newState);
|
||||||
|
th.setAttribute('data-sort-state', newState);
|
||||||
|
|
||||||
|
if (newState === 'none') {
|
||||||
|
rows.sort((a, b) => parseInt(a.getAttribute('data-index')) - parseInt(b.getAttribute('data-index')));
|
||||||
|
} else {
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const aText = a.cells[columnIndex].textContent.trim();
|
||||||
|
const bText = b.cells[columnIndex].textContent.trim();
|
||||||
|
|
||||||
|
const aNum = parseFloat(aText.replace(/[^0-9.-]/g, ''));
|
||||||
|
const bNum = parseFloat(bText.replace(/[^0-9.-]/g, ''));
|
||||||
|
const isNumeric = !isNaN(aNum) && !isNaN(bNum);
|
||||||
|
|
||||||
|
return newState === 'asc'
|
||||||
|
? (isNumeric ? aNum - bNum : aText.localeCompare(bText))
|
||||||
|
: (isNumeric ? bNum - aNum : bText.localeCompare(aText));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadArpTable() {
|
||||||
|
const tableBody = document.getElementById("arpTableBody");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/arp/json");
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
tableBody.innerHTML = "";
|
||||||
|
|
||||||
|
if (!data.arp_table || data.arp_table.length === 0) {
|
||||||
|
tableBody.innerHTML = "<tr><td colspan='4' class='text-center'>Geen data beschikbaar.</td></tr>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.arp_table.forEach((entry, index) => {
|
||||||
|
const row = document.createElement("tr");
|
||||||
|
row.setAttribute("data-index", index);
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${entry.address}</td>
|
||||||
|
<td>${entry.interface}</td>
|
||||||
|
<td>${entry.link_layer_address}</td>
|
||||||
|
<td>${entry.state}</td>
|
||||||
|
`;
|
||||||
|
tableBody.appendChild(row);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
tableBody.innerHTML = "<tr><td colspan='4' class='text-center text-danger'>Fout bij ophalen van data.</td></tr>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadArpTable();
|
||||||
|
});
|
||||||
|
|
||||||
|
function refreshArpTable() {
|
||||||
|
const searchInput = document.getElementById("arpSearch");
|
||||||
|
const refreshIcon = document.getElementById("refreshIcon");
|
||||||
|
const refreshSpinner = document.getElementById("refreshSpinner");
|
||||||
|
|
||||||
|
searchInput.value = ""; // Leegmaken van het zoekveld
|
||||||
|
|
||||||
|
refreshIcon.classList.add("d-none");
|
||||||
|
refreshSpinner.classList.remove("d-none");
|
||||||
|
|
||||||
|
loadArpTable().then(() => {
|
||||||
|
refreshIcon.classList.remove("d-none");
|
||||||
|
refreshSpinner.classList.add("d-none");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
156
templates/bgp-route.html
Normal file
156
templates/bgp-route.html
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AS215085 - Router tools | BGP route lookup</title>
|
||||||
|
<link rel="icon" type="image/png" href="../static/img/cropped-Pixelhosting-logo-favicon-32x32.png">
|
||||||
|
<script src="../static/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../static/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-content">
|
||||||
|
<a href="/"> <img src="../static/img/as215085-logo.png" alt="AS215085 Logo" style="height: 120px; opacity: 1;!important"> </a>
|
||||||
|
<h2><b>core1.doet.pixelhosting.nl</b></h2>
|
||||||
|
<p><b>Proudly delivering the backbone for PixelHosting’s services</b></p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #07AAF9;">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav justify-content-center w-100">
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp">BGP summary</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp-route">BGP route</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/arp">ARP table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/neighbors">Neighbors table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/interfaces">Interfaces table</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<section id="bgp-route-lookup">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="prefixInput"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Type the prefix here, for example, 2606:4700:4700::/48 or 1.1.1.0/24"
|
||||||
|
aria-label="Enter a prefix"
|
||||||
|
onkeydown="if(event.key === 'Enter') loadBGPRoute()"
|
||||||
|
/>
|
||||||
|
<button class="btn btn-outline-primary" type="button" onclick="loadBGPRoute()">Lookup</button>
|
||||||
|
</div>
|
||||||
|
<pre id="bgpOutput" aria-label="BGP Route Lookup output">Enter a prefix and click Lookup.</pre>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer class="text-center py-3">
|
||||||
|
<p>© 2020 – <span id="year"></span> AS215085 (PixelHosting). All rights reserved.</p>
|
||||||
|
<script>document.getElementById("year").textContent = new Date().getFullYear();</script>
|
||||||
|
</footer>
|
||||||
|
<script src="../static/js/materialize.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function detectIpVersion(ip) {
|
||||||
|
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
|
||||||
|
const ipv6Regex = /^([0-9a-fA-F:]+)(\/\d{1,3})?$/;
|
||||||
|
|
||||||
|
if (ipv4Regex.test(ip)) {
|
||||||
|
return "ipv4";
|
||||||
|
} else if (ipv6Regex.test(ip)) {
|
||||||
|
return "ipv6";
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightBGPOutput(text) {
|
||||||
|
text = text.replace(/\b215085\b/g, '<span class="asn215085-highlight">215085</span>');
|
||||||
|
|
||||||
|
text = text.replace(/\b\d{4,6}\b/g, match => {
|
||||||
|
if (match === '215085') return match;
|
||||||
|
return `<span class="asn-highlight">${match}</span>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
text = text.split('\n').map(line => {
|
||||||
|
if (line.toLowerCase().includes('best')) {
|
||||||
|
return `<strong class="best-line">${line}</strong>`;
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
text = text.split('\n').map(line => {
|
||||||
|
if (line.toLowerCase().includes('table entry')) {
|
||||||
|
return `<strong class="best-line">${line}</strong>`;
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
text = text.split('\n').map(line => {
|
||||||
|
if (line.toLowerCase().includes('multipath')) {
|
||||||
|
return `<strong class="best-line">${line}</strong>`;
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return text.replace(/\n/g, '<br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayBGPRoute(data) {
|
||||||
|
const outputElem = document.getElementById("bgpOutput");
|
||||||
|
const rawText = data.data || JSON.stringify(data, null, 2);
|
||||||
|
const highlighted = highlightBGPOutput(rawText);
|
||||||
|
outputElem.innerHTML = highlighted;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBGPRoute() {
|
||||||
|
const outputElem = document.getElementById("bgpOutput");
|
||||||
|
const prefix = document.getElementById("prefixInput").value.trim();
|
||||||
|
|
||||||
|
if (!prefix) {
|
||||||
|
outputElem.textContent = "Voer eerst een geldig prefix in.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ip_version = detectIpVersion(prefix);
|
||||||
|
if (!ip_version) {
|
||||||
|
outputElem.textContent = "Ongeldig IP-adres of prefix.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputElem.textContent = "Loading...";
|
||||||
|
|
||||||
|
const postData = {
|
||||||
|
ip_version: ip_version,
|
||||||
|
bgprouteprefix: prefix
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/bgp-route/lookup", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(postData),
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error("Network response was not ok");
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
outputElem.textContent = "Error: " + data.error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayBGPRoute(data);
|
||||||
|
} catch (error) {
|
||||||
|
outputElem.textContent = "Fout bij ophalen van data: " + error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
297
templates/bgp.html
Normal file
297
templates/bgp.html
Normal file
|
|
@ -0,0 +1,297 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AS215085 - Router tools | BGP table</title>
|
||||||
|
<link rel="icon" type="image/png" href="../static/img/cropped-Pixelhosting-logo-favicon-32x32.png">
|
||||||
|
<script src="../static/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../static/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-content">
|
||||||
|
<a href="/"> <img src="../static/img/as215085-logo.png" alt="AS215085 Logo" style="height: 120px; opacity: 1;!important"> </a>
|
||||||
|
<h2><b>core1.doet.pixelhosting.nl</b></h2>
|
||||||
|
<p><b>Proudly delivering the backbone for PixelHosting’s services</b></p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #07AAF9;">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav justify-content-center w-100">
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp">BGP summary</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp-route">BGP route</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/arp">ARP table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/neighbors">Neighbors table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/interfaces">Interfaces table</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<section id="bgp-v4">
|
||||||
|
<h4>IPv4 Unicast Summary</h4>
|
||||||
|
<p id="ipv4Summary">Loading summary...</p>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" id="ipv4Search" class="form-control" placeholder="Search in IPv4 table..." onkeyup="filterTable('ipv4Search', 'ipv4Table')">
|
||||||
|
<button id="refreshButton" class="btn btn-outline-primary" type="button" onclick="refreshBGPTable()">
|
||||||
|
<span id="refreshIcon">⟳</span>
|
||||||
|
<span id="refreshSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
Refresh data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table class="striped" id="ipv4Table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 0, this)">Neighbor <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 1, this)">Version <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 2, this)">AS Number <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 3, this)">Messages Received <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 4, this)">Messages Sent <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 5, this)">Table Version <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 6, this)">Inbound Queue <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 7, this)">Outbound Queue <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 8, this)">Up/Down <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 9, this)">State/PfxRcd <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 10, this)">Prefix Sent <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv4Table', 11, this)">Description <span class="sort-arrow"></span></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="ipv4TableBody">
|
||||||
|
<tr>
|
||||||
|
<td colspan="12" class="text-center">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><a href="/bgp/json">JSON version</a></p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="bgp-v6">
|
||||||
|
<h4>IPv6 Unicast Summary</h4>
|
||||||
|
<p id="ipv6Summary">Loading summary...</p>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" id="ipv6Search" class="form-control" placeholder="Search in IPv6 table..." onkeyup="filterTable('ipv6Search', 'ipv6Table')">
|
||||||
|
<button id="refreshButton" class="btn btn-outline-primary" type="button" onclick="refreshBGPTable()">
|
||||||
|
<span id="refreshIcon2">⟳</span>
|
||||||
|
<span id="refreshSpinner2" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
Refresh data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table class="striped" id="ipv6Table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 0, this)">Neighbor <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 1, this)">Version <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 2, this)">AS Number <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 3, this)">Messages Received <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 4, this)">Messages Sent <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 5, this)">Table Version <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 6, this)">Inbound Queue <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 7, this)">Outbound Queue <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 8, this)">Up/Down <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 9, this)">State/PfxRcd <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 10, this)">Prefix Sent <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('ipv6Table', 11, this)">Description <span class="sort-arrow"></span></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="ipv6TableBody">
|
||||||
|
<tr>
|
||||||
|
<td colspan="12" class="text-center">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><a href="/bgp/json">JSON version</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© 2020 – <span id="year"></span> AS215085 (PixelHosting). All rights reserved.</p>
|
||||||
|
<script> document.getElementById("year").textContent = new Date().getFullYear(); </script>
|
||||||
|
</footer>
|
||||||
|
<script src="../static/js/materialize.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function filterTable(searchInputId, tableId) {
|
||||||
|
const input = document.getElementById(searchInputId);
|
||||||
|
const filter = input.value.toUpperCase();
|
||||||
|
const table = document.getElementById(tableId);
|
||||||
|
const tr = table.getElementsByTagName("tr");
|
||||||
|
|
||||||
|
for (let i = 1; i < tr.length; i++) {
|
||||||
|
tr[i].style.display = "none";
|
||||||
|
const td = tr[i].getElementsByTagName("td");
|
||||||
|
for (let j = 0; j < td.length; j++) {
|
||||||
|
if (td[j]) {
|
||||||
|
const txtValue = td[j].textContent || td[j].innerText;
|
||||||
|
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||||
|
tr[i].style.display = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTable(tableId, columnIndex, th) {
|
||||||
|
const table = document.getElementById(tableId);
|
||||||
|
const tbody = table.tBodies[0];
|
||||||
|
const rows = Array.from(tbody.rows);
|
||||||
|
const headers = Array.from(th.parentNode.children);
|
||||||
|
|
||||||
|
headers.forEach(header => {
|
||||||
|
if (header !== th) {
|
||||||
|
header.classList.remove('asc', 'desc');
|
||||||
|
header.setAttribute('data-sort-state', 'none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentState = th.getAttribute('data-sort-state') || 'none';
|
||||||
|
let newState = currentState === 'none' ? 'asc' : currentState === 'asc' ? 'desc' : 'none';
|
||||||
|
|
||||||
|
th.classList.remove('asc', 'desc');
|
||||||
|
if (newState !== 'none') th.classList.add(newState);
|
||||||
|
th.setAttribute('data-sort-state', newState);
|
||||||
|
|
||||||
|
if (newState === 'none') {
|
||||||
|
rows.sort((a, b) => parseInt(a.getAttribute('data-index')) - parseInt(b.getAttribute('data-index')));
|
||||||
|
} else {
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const aText = a.cells[columnIndex].textContent.trim();
|
||||||
|
const bText = b.cells[columnIndex].textContent.trim();
|
||||||
|
|
||||||
|
const aNum = parseFloat(aText.replace(/[^0-9.-]/g, ''));
|
||||||
|
const bNum = parseFloat(bText.replace(/[^0-9.-]/g, ''));
|
||||||
|
const isNumeric = !isNaN(aNum) && !isNaN(bNum);
|
||||||
|
|
||||||
|
return newState === 'asc'
|
||||||
|
? (isNumeric ? aNum - bNum : aText.localeCompare(bText))
|
||||||
|
: (isNumeric ? bNum - aNum : bText.localeCompare(aText));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBgpTables() {
|
||||||
|
const ipv4SummaryEl = document.getElementById("ipv4Summary");
|
||||||
|
const ipv6SummaryEl = document.getElementById("ipv6Summary");
|
||||||
|
const ipv4Body = document.getElementById("ipv4TableBody");
|
||||||
|
const ipv6Body = document.getElementById("ipv6TableBody");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/bgp/json");
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
ipv4SummaryEl.innerHTML = `
|
||||||
|
<p>BGP Router ID: ${data.ipv4_info.router_id}, Local AS Number: ${data.ipv4_info.local_as} VRF ID: ${data.ipv4_info.vrf_id}</p>
|
||||||
|
<p>BGP Table Version: ${data.ipv4_info.table_version}</p>
|
||||||
|
<p>RIB Entries: ${data.ipv4_info.rib_entries}, using ${data.ipv4_info.rib_memory}</p>
|
||||||
|
<p>Peers: ${data.ipv4_info.peers}, using ${data.ipv4_info.peers_memory}</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
ipv6SummaryEl.innerHTML = `
|
||||||
|
<p>BGP Router ID: ${data.ipv6_info.router_id}, Local AS Number: ${data.ipv6_info.local_as} VRF ID: ${data.ipv6_info.vrf_id}</p>
|
||||||
|
<p>BGP Table Version: ${data.ipv6_info.table_version}</p>
|
||||||
|
<p>RIB Entries: ${data.ipv6_info.rib_entries}, using ${data.ipv6_info.rib_memory}</p>
|
||||||
|
<p>Peers: ${data.ipv6_info.peers}, using ${data.ipv6_info.peers_memory}</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
ipv4Body.innerHTML = "";
|
||||||
|
if (data.ipv4_peers.length === 0) {
|
||||||
|
ipv4Body.innerHTML = `<tr><td colspan="12" class="text-center">Geen data beschikbaar.</td></tr>`;
|
||||||
|
} else {
|
||||||
|
data.ipv4_peers.forEach((peer, index) => {
|
||||||
|
const row = document.createElement("tr");
|
||||||
|
row.setAttribute("data-index", index);
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${peer.neighbor}</td>
|
||||||
|
<td>${peer.version}</td>
|
||||||
|
<td>${peer.as_number}</td>
|
||||||
|
<td>${peer.msg_received}</td>
|
||||||
|
<td>${peer.msg_sent}</td>
|
||||||
|
<td>${peer.table_version}</td>
|
||||||
|
<td>${peer.in_queue}</td>
|
||||||
|
<td>${peer.out_queue}</td>
|
||||||
|
<td>${peer.up_down}</td>
|
||||||
|
<td>${peer.state_pfx_rcd}</td>
|
||||||
|
<td>${peer.prefix_sent}</td>
|
||||||
|
<td>${peer.description}</td>
|
||||||
|
`;
|
||||||
|
ipv4Body.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv6Body.innerHTML = "";
|
||||||
|
if (data.ipv6_peers.length === 0) {
|
||||||
|
ipv6Body.innerHTML = `<tr><td colspan="12" class="text-center">Geen data beschikbaar.</td></tr>`;
|
||||||
|
} else {
|
||||||
|
data.ipv6_peers.forEach((peer, index) => {
|
||||||
|
const row = document.createElement("tr");
|
||||||
|
row.setAttribute("data-index", index);
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${peer.neighbor}</td>
|
||||||
|
<td>${peer.version}</td>
|
||||||
|
<td>${peer.as_number}</td>
|
||||||
|
<td>${peer.msg_received}</td>
|
||||||
|
<td>${peer.msg_sent}</td>
|
||||||
|
<td>${peer.table_version}</td>
|
||||||
|
<td>${peer.in_queue}</td>
|
||||||
|
<td>${peer.out_queue}</td>
|
||||||
|
<td>${peer.up_down}</td>
|
||||||
|
<td>${peer.state_pfx_rcd}</td>
|
||||||
|
<td>${peer.prefix_sent}</td>
|
||||||
|
<td>${peer.description}</td>
|
||||||
|
`;
|
||||||
|
ipv6Body.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ipv4SummaryEl.textContent = "Fout bij ophalen IPv4 data.";
|
||||||
|
ipv6SummaryEl.textContent = "Fout bij ophalen IPv6 data.";
|
||||||
|
ipv4Body.innerHTML = `<tr><td colspan="12" class="text-center text-danger">Fout bij ophalen van data.</td></tr>`;
|
||||||
|
ipv6Body.innerHTML = `<tr><td colspan="12" class="text-center text-danger">Fout bij ophalen van data.</td></tr>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadBgpTables();
|
||||||
|
});
|
||||||
|
|
||||||
|
function refreshBGPTable() {
|
||||||
|
const searchInput = document.getElementById("ipv4Search");
|
||||||
|
const searchInput2 = document.getElementById("ipv6Search");
|
||||||
|
const refreshIcon = document.getElementById("refreshIcon");
|
||||||
|
const refreshIcon2 = document.getElementById("refreshIcon2");
|
||||||
|
const refreshSpinner = document.getElementById("refreshSpinner");
|
||||||
|
const refreshSpinner2 = document.getElementById("refreshSpinner2");
|
||||||
|
|
||||||
|
searchInput.value = ""; // Leegmaken van het zoekveld
|
||||||
|
searchInput2.value = ""; // Leegmaken van het zoekveld
|
||||||
|
|
||||||
|
refreshIcon.classList.add("d-none");
|
||||||
|
refreshIcon2.classList.add("d-none");
|
||||||
|
refreshSpinner.classList.remove("d-none");
|
||||||
|
refreshSpinner2.classList.remove("d-none");
|
||||||
|
|
||||||
|
loadBgpTables().then(() => {
|
||||||
|
refreshIcon.classList.remove("d-none");
|
||||||
|
refreshIcon2.classList.remove("d-none");
|
||||||
|
refreshSpinner.classList.add("d-none");
|
||||||
|
refreshSpinner2.classList.add("d-none");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
64
templates/index.html
Normal file
64
templates/index.html
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AS215085 - Router tools | Home</title>
|
||||||
|
<link rel="icon" type="image/png" href="../static/img/cropped-Pixelhosting-logo-favicon-32x32.png">
|
||||||
|
<script src="../static/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../static/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-content">
|
||||||
|
<a href="/"> <img src="../static/img/as215085-logo.png" alt="AS215085 Logo" style="height: 120px; opacity: 1;!important"> </a>
|
||||||
|
<h2><b>core1.doet.pixelhosting.nl</b></h2>
|
||||||
|
<p><b>Proudly delivering the backbone for PixelHosting’s services</b></p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #07AAF9;">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav justify-content-center w-100">
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp">BGP summary</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp-route">BGP route</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/arp">ARP table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/neighbors">Neighbors table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/interfaces">Interfaces table</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<section id="overview">
|
||||||
|
<h2>Welcome</h2>
|
||||||
|
<p>Welcome to the <b>AS215085 Network tools</b></p>
|
||||||
|
<p>On this tool you cn get in depth insight into our network</p>
|
||||||
|
<p>In case of emergency you can contact us at: noc AT pixelhosting DOT nl</p>
|
||||||
|
<p>PeeringDB: <a href="https://www.peeringdb.com/net/35968" target="_blank" style="color: #07AAF9;">https://www.peeringdb.com/net/35968</a></p>
|
||||||
|
<p>BGPTools: <a href="https://bgp.tools/as/215085" target="_blank" style="color: #07AAF9;">https://bgp.tools/as/215085</a></p>
|
||||||
|
<p>Lookingglas: <a href="https://lookingglass.as215085.net" target="_blank" style="color: #07AAF9;">https://lookingglass.as215085.net</a></p>
|
||||||
|
<p>Geofeed: <a href="https://as215085.net/geofeed.csv" target="_blank" style="color: #07AAF9;">https://as215085.net/geofeed.csv</a></p>
|
||||||
|
</section>
|
||||||
|
<section id="status">
|
||||||
|
<h2>Status</h2>
|
||||||
|
<p>Here you can find the network status of <b>AS215085</b>: <a href="https://status.as215085.net" target="_blank" style="color: #07AAF9;">https://status.as215085.net</a></p>
|
||||||
|
<p>Here you can find the service status of <b>PixelHosting</b>: <a href="https://status.pixelhosting.nl/" target="_blank" style="color: #07AAF9;">https://status.pixelhosting.nl</a></p>
|
||||||
|
</section>
|
||||||
|
<section id="bgptools-map">
|
||||||
|
<h2>BGP.Tools network map</h2>
|
||||||
|
<img id="pathimg" usemap="#world" src="{{ bgp_image }}">
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© 2020 – <span id="year"></span> AS215085 (PixelHosting). All rights reserved.</p>
|
||||||
|
<script> document.getElementById("year").textContent = new Date().getFullYear(); </script>
|
||||||
|
</footer>
|
||||||
|
<script src="../static/js/materialize.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
197
templates/interfaces.html
Normal file
197
templates/interfaces.html
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AS215085 - Router tools | Interfaces</title>
|
||||||
|
<link rel="icon" type="image/png" href="../static/img/cropped-Pixelhosting-logo-favicon-32x32.png">
|
||||||
|
<script src="../static/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../static/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-content">
|
||||||
|
<a href="/"> <img src="../static/img/as215085-logo.png" alt="AS215085 Logo" style="height: 120px; opacity: 1;!important"> </a>
|
||||||
|
<h2><b>core1.doet.pixelhosting.nl</b></h2>
|
||||||
|
<p><b>Proudly delivering the backbone for PixelHosting’s services</b></p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #07AAF9;">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav justify-content-center w-100">
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp">BGP summary</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp-route">BGP route</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/arp">ARP table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/neighbors">Neighbors table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/interfaces">Interfaces table</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<section id="interfaces">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" id="interfaceSearch" class="form-control" placeholder="Search in Interface table..." onkeyup="filterTable('interfaceSearch', 'interfaceTable')">
|
||||||
|
<button id="refreshButton" class="btn btn-outline-primary" type="button" onclick="refreshTable()">
|
||||||
|
<span id="refreshIcon">⟳</span>
|
||||||
|
<span id="refreshSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
Refresh data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p></p> <p>Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down</p>
|
||||||
|
<table class="striped table table-bordered" id="interfaceTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="sortable" onclick="sortTable('interfaceTable', 0, this)">Interface <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('interfaceTable', 1, this)">IP Address <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('interfaceTable', 2, this)">MAC Address <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('interfaceTable', 3, this)">VRF <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('interfaceTable', 4, this)">MTU <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('interfaceTable', 5, this)">Status <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('interfaceTable', 6, this)">Description <span class="sort-arrow"></span></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="interfaceTableBody">
|
||||||
|
<tr id="interface-loading-row">
|
||||||
|
<td colspan="7" class="text-center">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><a href="/interfaces/json">JSON version</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© 2020 – <span id="year"></span> AS215085 (PixelHosting). All rights reserved.</p>
|
||||||
|
<script> document.getElementById("year").textContent = new Date().getFullYear(); </script>
|
||||||
|
</footer>
|
||||||
|
<script src="../static/js/materialize.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function filterTable(searchInputId, tableId) {
|
||||||
|
const input = document.getElementById(searchInputId);
|
||||||
|
const filter = input.value.toUpperCase();
|
||||||
|
const table = document.getElementById(tableId);
|
||||||
|
const tr = table.getElementsByTagName("tr");
|
||||||
|
|
||||||
|
for (let i = 1; i < tr.length; i++) {
|
||||||
|
tr[i].style.display = "none";
|
||||||
|
const td = tr[i].getElementsByTagName("td");
|
||||||
|
for (let j = 0; j < td.length; j++) {
|
||||||
|
if (td[j]) {
|
||||||
|
const txtValue = td[j].textContent || td[j].innerText;
|
||||||
|
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||||
|
tr[i].style.display = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTable(tableId, columnIndex, th) {
|
||||||
|
const table = document.getElementById(tableId);
|
||||||
|
const tbody = table.tBodies[0];
|
||||||
|
const rows = Array.from(tbody.rows);
|
||||||
|
const headers = Array.from(th.parentNode.children);
|
||||||
|
|
||||||
|
// Reset other headers
|
||||||
|
headers.forEach(header => {
|
||||||
|
if (header !== th) {
|
||||||
|
header.classList.remove('asc', 'desc');
|
||||||
|
header.setAttribute('data-sort-state', 'none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentState = th.getAttribute('data-sort-state') || 'none';
|
||||||
|
let newState = currentState === 'none' ? 'asc' : currentState === 'asc' ? 'desc' : 'none';
|
||||||
|
|
||||||
|
th.classList.remove('asc', 'desc');
|
||||||
|
if (newState !== 'none') th.classList.add(newState);
|
||||||
|
th.setAttribute('data-sort-state', newState);
|
||||||
|
|
||||||
|
if (newState === 'none') {
|
||||||
|
rows.sort((a, b) => parseInt(a.getAttribute('data-index')) - parseInt(b.getAttribute('data-index')));
|
||||||
|
} else {
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const aText = a.cells[columnIndex].textContent.trim();
|
||||||
|
const bText = b.cells[columnIndex].textContent.trim();
|
||||||
|
const aNum = parseFloat(aText.replace(/[^0-9.-]/g, ''));
|
||||||
|
const bNum = parseFloat(bText.replace(/[^0-9.-]/g, ''));
|
||||||
|
const isNumeric = !isNaN(aNum) && !isNaN(bNum);
|
||||||
|
|
||||||
|
return newState === 'asc'
|
||||||
|
? (isNumeric ? aNum - bNum : aText.localeCompare(bText))
|
||||||
|
: (isNumeric ? bNum - aNum : bText.localeCompare(aText));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadInterfaceTable() {
|
||||||
|
const tableBody = document.getElementById("interfaceTableBody");
|
||||||
|
const loadingRow = document.getElementById("interface-loading-row");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/interfaces/json");
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Leeg de body
|
||||||
|
tableBody.innerHTML = "";
|
||||||
|
|
||||||
|
if (!data.interface_table || data.interface_table.length === 0) {
|
||||||
|
tableBody.innerHTML = "<tr><td colspan='7' class='text-center'>Geen data beschikbaar.</td></tr>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.interface_table.forEach((entry, index) => {
|
||||||
|
const row = document.createElement("tr");
|
||||||
|
row.setAttribute("data-index", index);
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${entry.interface}</td>
|
||||||
|
<td>${entry.ip_address}</td>
|
||||||
|
<td>${entry.mac_address}</td>
|
||||||
|
<td>${entry.vrf}</td>
|
||||||
|
<td>${entry.mtu}</td>
|
||||||
|
<td>${entry.status}</td>
|
||||||
|
<td>${entry.description}</td>
|
||||||
|
`;
|
||||||
|
tableBody.appendChild(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
tableBody.innerHTML = "<tr><td colspan='7' class='text-center text-danger'>Fout bij ophalen van data.</td></tr>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadInterfaceTable();
|
||||||
|
});
|
||||||
|
|
||||||
|
function refreshTable() {
|
||||||
|
const searchInput = document.getElementById("interfaceSearch");
|
||||||
|
const refreshIcon = document.getElementById("refreshIcon");
|
||||||
|
const refreshSpinner = document.getElementById("refreshSpinner");
|
||||||
|
|
||||||
|
searchInput.value = ""; // Leegmaken van het zoekveld
|
||||||
|
|
||||||
|
refreshIcon.classList.add("d-none");
|
||||||
|
refreshSpinner.classList.remove("d-none");
|
||||||
|
|
||||||
|
loadInterfaceTable().then(() => {
|
||||||
|
refreshIcon.classList.remove("d-none");
|
||||||
|
refreshSpinner.classList.add("d-none");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
187
templates/neighbors.html
Normal file
187
templates/neighbors.html
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AS215085 - Router tools | Neighbors</title>
|
||||||
|
<link rel="icon" type="image/png" href="../static/img/cropped-Pixelhosting-logo-favicon-32x32.png">
|
||||||
|
<script src="../static/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../static/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-content">
|
||||||
|
<a href="/"> <img src="../static/img/as215085-logo.png" alt="AS215085 Logo" style="height: 120px; opacity: 1;!important"> </a>
|
||||||
|
<h2><b>core1.doet.pixelhosting.nl</b></h2>
|
||||||
|
<p><b>Proudly delivering the backbone for PixelHosting’s services</b></p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #07AAF9;">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav justify-content-center w-100">
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp">BGP summary</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/bgp-route">BGP route</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/arp">ARP table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/neighbors">Neighbors table</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link text-white" href="/interfaces">Interfaces table</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<section id="arp">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" id="neighborsSearch" class="form-control" placeholder="Search in Neighbors table..." onkeyup="filterTable('neighborsSearch', 'neighborsTable')">
|
||||||
|
<button id="refreshButton" class="btn btn-outline-primary" type="button" onclick="refreshNeighborsTable()">
|
||||||
|
<span id="refreshIcon">⟳</span>
|
||||||
|
<span id="refreshSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
Refresh data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table class="striped table table-bordered" id="neighborTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="sortable" onclick="sortTable('neighborsTable', 0, this)">Address <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('neighborsTable', 1, this)">Interface <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('neighborsTable', 2, this)">Link Layer Address <span class="sort-arrow"></span></th>
|
||||||
|
<th class="sortable" onclick="sortTable('neighborsTable', 3, this)">State <span class="sort-arrow"></span></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="neighborsTableBody">
|
||||||
|
<tr id="arp-loading-row">
|
||||||
|
<td colspan="4" class="text-center">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><a href="/neighbors/json">JSON version</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© 2020 – <span id="year"></span> AS215085 (PixelHosting). All rights reserved.</p>
|
||||||
|
<script> document.getElementById("year").textContent = new Date().getFullYear(); </script>
|
||||||
|
</footer>
|
||||||
|
<script src="../static/js/materialize.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function filterTable(searchInputId, tableId) {
|
||||||
|
const input = document.getElementById(searchInputId);
|
||||||
|
const filter = input.value.toUpperCase();
|
||||||
|
const table = document.getElementById(tableId);
|
||||||
|
const tr = table.getElementsByTagName("tr");
|
||||||
|
|
||||||
|
for (let i = 1; i < tr.length; i++) {
|
||||||
|
tr[i].style.display = "none";
|
||||||
|
const td = tr[i].getElementsByTagName("td");
|
||||||
|
for (let j = 0; j < td.length; j++) {
|
||||||
|
if (td[j]) {
|
||||||
|
const txtValue = td[j].textContent || td[j].innerText;
|
||||||
|
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||||
|
tr[i].style.display = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTable(tableId, columnIndex, th) {
|
||||||
|
const table = document.getElementById(tableId);
|
||||||
|
const tbody = table.tBodies[0];
|
||||||
|
const rows = Array.from(tbody.rows);
|
||||||
|
const headers = Array.from(th.parentNode.children);
|
||||||
|
|
||||||
|
headers.forEach(header => {
|
||||||
|
if (header !== th) {
|
||||||
|
header.classList.remove('asc', 'desc');
|
||||||
|
header.setAttribute('data-sort-state', 'none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentState = th.getAttribute('data-sort-state') || 'none';
|
||||||
|
let newState = currentState === 'none' ? 'asc' : currentState === 'asc' ? 'desc' : 'none';
|
||||||
|
|
||||||
|
th.classList.remove('asc', 'desc');
|
||||||
|
if (newState !== 'none') th.classList.add(newState);
|
||||||
|
th.setAttribute('data-sort-state', newState);
|
||||||
|
|
||||||
|
if (newState === 'none') {
|
||||||
|
rows.sort((a, b) => parseInt(a.getAttribute('data-index')) - parseInt(b.getAttribute('data-index')));
|
||||||
|
} else {
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const aText = a.cells[columnIndex].textContent.trim();
|
||||||
|
const bText = b.cells[columnIndex].textContent.trim();
|
||||||
|
|
||||||
|
const aNum = parseFloat(aText.replace(/[^0-9.-]/g, ''));
|
||||||
|
const bNum = parseFloat(bText.replace(/[^0-9.-]/g, ''));
|
||||||
|
const isNumeric = !isNaN(aNum) && !isNaN(bNum);
|
||||||
|
|
||||||
|
return newState === 'asc'
|
||||||
|
? (isNumeric ? aNum - bNum : aText.localeCompare(bText))
|
||||||
|
: (isNumeric ? bNum - aNum : bText.localeCompare(aText));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadneighborsTable() {
|
||||||
|
const tableBody = document.getElementById("neighborsTableBody");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/neighbors/json");
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
tableBody.innerHTML = "";
|
||||||
|
|
||||||
|
if (!data.neighbors_table || data.neighbors_table.length === 0) {
|
||||||
|
tableBody.innerHTML = "<tr><td colspan='4' class='text-center'>Geen data beschikbaar.</td></tr>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.neighbors_table.forEach((entry, index) => {
|
||||||
|
const row = document.createElement("tr");
|
||||||
|
row.setAttribute("data-index", index);
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${entry.address}</td>
|
||||||
|
<td>${entry.interface}</td>
|
||||||
|
<td>${entry.link_layer_address}</td>
|
||||||
|
<td>${entry.state}</td>
|
||||||
|
`;
|
||||||
|
tableBody.appendChild(row);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
tableBody.innerHTML = "<tr><td colspan='4' class='text-center text-danger'>Fout bij ophalen van data.</td></tr>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadneighborsTable();
|
||||||
|
});
|
||||||
|
|
||||||
|
function refreshNeighborsTable() {
|
||||||
|
const searchInput = document.getElementById("neighborsSearch");
|
||||||
|
const refreshIcon = document.getElementById("refreshIcon");
|
||||||
|
const refreshSpinner = document.getElementById("refreshSpinner");
|
||||||
|
|
||||||
|
searchInput.value = ""; // Leegmaken van het zoekveld
|
||||||
|
|
||||||
|
refreshIcon.classList.add("d-none");
|
||||||
|
refreshSpinner.classList.remove("d-none");
|
||||||
|
|
||||||
|
loadneighborsTable().then(() => {
|
||||||
|
refreshIcon.classList.remove("d-none");
|
||||||
|
refreshSpinner.classList.add("d-none");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue