initial commit
This commit is contained in:
1 parent
af59a90c31
commit
ef498e4a72
114 files changed
+11047
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,21 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/UploadFile.html"
|
||||
|
||||
|
||||
def get_backup_manager(session) -> dict:
|
||||
"""Returns the current backup-config form fields (method, server
|
||||
address, etc.) — this page is a config form, not a data table, so the
|
||||
values reflect whatever was last configured/left blank."""
|
||||
tables = fetch_tables(session, PATH)
|
||||
return tables["Backup"]["record"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
print(json.dumps(get_backup_manager(session), indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,20 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/BufferedLogs.html"
|
||||
|
||||
|
||||
def get_buffered_log(session) -> list[dict]:
|
||||
"""Returns one dict per log entry (Log Index, Severity, Log Time,
|
||||
Component, Description, Message Text)."""
|
||||
tables = fetch_tables(session, PATH)
|
||||
return tables["Log Message"]["rows"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
print(json.dumps(get_buffered_log(session), indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,20 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/Clock_Detail.html"
|
||||
|
||||
|
||||
def get_clock(session) -> dict:
|
||||
"""Returns {"Current Time": record, "Time Zone": record,
|
||||
"Daylight Saving Time": record}."""
|
||||
tables = fetch_tables(session, PATH)
|
||||
return {caption: table["record"] for caption, table in tables.items()}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
print(json.dumps(get_clock(session), indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,22 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/LLDPStats.html"
|
||||
|
||||
|
||||
def get_lldp_statistics(session) -> dict:
|
||||
"""Returns {"global": record, "interfaces": list[dict]}."""
|
||||
tables = fetch_tables(session, PATH)
|
||||
return {
|
||||
"global": tables["LLDP Global Statistics"]["record"],
|
||||
"interfaces": tables["LLDP Interface Statistics"]["rows"],
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
print(json.dumps(get_lldp_statistics(session), indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,18 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/Logging.html"
|
||||
|
||||
|
||||
def get_log_configuration(session) -> dict:
|
||||
tables = fetch_tables(session, PATH)
|
||||
return tables["Logging"]["record"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
print(json.dumps(get_log_configuration(session), indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,20 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/Network.html"
|
||||
|
||||
|
||||
def get_network_setup(session) -> dict:
|
||||
"""Returns {table_caption: record} for Network Details, Web Parameters,
|
||||
Management Access and SNMP."""
|
||||
tables = fetch_tables(session, PATH)
|
||||
return {caption: table["record"] for caption, table in tables.items()}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
print(json.dumps(get_network_setup(session), indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,21 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/PortSummary.html"
|
||||
|
||||
|
||||
def get_port_summary(session) -> list[dict]:
|
||||
"""Returns one dict per port (Interface, Physical Type, Port Status,
|
||||
AutoNeg Status, Link Speed, MTU)."""
|
||||
tables = fetch_tables(session, PATH)
|
||||
return tables["Port Summary"]["rows"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
ports = get_port_summary(session)
|
||||
print(json.dumps(ports, indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,18 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/SNTP.html"
|
||||
|
||||
|
||||
def get_sntp(session) -> dict:
|
||||
tables = fetch_tables(session, PATH)
|
||||
return tables["SNTP Configuration"]["record"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
print(json.dumps(get_sntp(session), indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,18 @@
|
||||
import json
|
||||
|
||||
import switch_client
|
||||
import todo
|
||||
from extractors.xe_page import fetch_tables
|
||||
|
||||
PATH = "/SysDescription.html"
|
||||
|
||||
|
||||
def get_system_description(session) -> dict:
|
||||
tables = fetch_tables(session, PATH)
|
||||
return tables["System Description"]["record"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = switch_client.get_session()
|
||||
print(json.dumps(get_system_description(session), indent=2))
|
||||
todo.mark_implemented(switch_client.BASE_URL + PATH)
|
||||
@@ -0,0 +1,76 @@
|
||||
"""Generic parser for the switch firmware's auto-generated ("XE") pages.
|
||||
|
||||
Every data page (SysDescription.html, PortSummary.html, BufferedLogs.html, ...)
|
||||
renders one or more <TABLE> elements in one of two shapes:
|
||||
|
||||
- "scalar": no <TH> headers, each <TR> is a label/value pair via
|
||||
<TD class=defleft> (label text) + <TD class=defright><INPUT VALUE=...>.
|
||||
e.g. Network.html's "Network Details" table.
|
||||
- "tabular": <TH> column headers followed by one <TR> per data row, each
|
||||
<TD> holding the cell value inside a hidden <INPUT VALUE=...>.
|
||||
e.g. PortSummary.html's one row per switch port.
|
||||
|
||||
This module parses either shape without needing per-page-specific code; each
|
||||
extractors/<name>.py module just calls fetch_tables() and picks out the
|
||||
table(s)/fields it cares about by caption.
|
||||
"""
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
import switch_client
|
||||
|
||||
|
||||
def extract_cell_value(td) -> str:
|
||||
inp = td.find("input")
|
||||
if inp is not None:
|
||||
return inp.get("value", "")
|
||||
return td.get_text(strip=True)
|
||||
|
||||
|
||||
def parse_table(table) -> dict:
|
||||
caption_tag = table.find("caption")
|
||||
caption = caption_tag.get_text(strip=True) if caption_tag else None
|
||||
headers = [th.get_text(strip=True) for th in table.find_all("th", recursive=False)]
|
||||
trs = table.find_all("tr", recursive=False)
|
||||
|
||||
if headers:
|
||||
rows = []
|
||||
for tr in trs:
|
||||
tds = tr.find_all("td", recursive=False)
|
||||
if len(tds) != len(headers):
|
||||
continue
|
||||
rows.append({h: extract_cell_value(td) for h, td in zip(headers, tds)})
|
||||
return {"caption": caption, "kind": "tabular", "rows": rows}
|
||||
|
||||
record = {}
|
||||
field_names = {}
|
||||
for tr in trs:
|
||||
defleft = tr.find("td", class_="defleft")
|
||||
defright = tr.find("td", class_="defright")
|
||||
if defleft is None or defright is None:
|
||||
continue
|
||||
label = defleft.get_text(strip=True)
|
||||
if not label:
|
||||
continue
|
||||
record[label] = extract_cell_value(defright)
|
||||
inp = defright.find("input")
|
||||
if inp is not None and inp.get("name"):
|
||||
field_names[label] = inp["name"]
|
||||
# field_names lets a write action look up the real <INPUT NAME=...> for a
|
||||
# label instead of hardcoding a guessed "v_R_C_1"-style field name.
|
||||
return {"caption": caption, "kind": "scalar", "record": record, "field_names": field_names}
|
||||
|
||||
|
||||
def parse_xe_tables(html: str) -> list[dict]:
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
return [parse_table(t) for t in soup.find_all("table")]
|
||||
|
||||
|
||||
def fetch_tables(session, path: str) -> dict[str, dict]:
|
||||
"""Fetch path and return {caption: table} for every captioned table.
|
||||
|
||||
Tables with no/blank caption (usually just a submit button row) are
|
||||
dropped since there's nothing useful to key them by."""
|
||||
html = switch_client.fetch(session, path)
|
||||
tables = parse_xe_tables(html)
|
||||
return {t["caption"]: t for t in tables if t["caption"]}
|
||||
Reference in new issue
Block a user