Update switch_backend.py for ERS 59100GTS-PWR+ (96+4 port)
Replace ERS 5952 (48+4 port) config with ERS 59100GTS-PWR+:
- Port validation extended to 1–100
- All interfaces now use GigabitEthernet 1/{p} slot notation
- PoE boundary moved from port 48 to port 96
- VLAN commands updated to use 1/{p} port notation
- Key path, TOTP name, and app title updated
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
This commit is contained in:
+24
-24
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
ERS 5952 Switch Manager — Backend v3
|
||||
ERS 59100GTS-PWR+ Switch Manager — Backend v3
|
||||
────────────────────────────────────────────────────────────────────────
|
||||
Changes from v2:
|
||||
- Connection pool (30s lifetime, liveness check, auto-invalidate)
|
||||
@@ -40,7 +40,7 @@ log = logging.getLogger("switch-manager")
|
||||
SWITCH_HOST = "192.168.99.1"
|
||||
SWITCH_PORT = 22
|
||||
SWITCH_USER = "admin"
|
||||
KEY_PATH = "/etc/switch-manager/ers5952_key"
|
||||
KEY_PATH = "/etc/switch-manager/ers59100_key"
|
||||
KNOWN_HOSTS = "/etc/switch-manager/known_hosts"
|
||||
TOTP_FILE = "/etc/switch-manager/totp_secret"
|
||||
STATIC_DIR = "./frontend/dist"
|
||||
@@ -71,9 +71,9 @@ def get_or_create_totp_secret() -> str:
|
||||
def setup_totp():
|
||||
secret = get_or_create_totp_secret()
|
||||
uri = pyotp.TOTP(secret).provisioning_uri(
|
||||
name="ERS5952", issuer_name="SwitchManager")
|
||||
name="ERS59100", issuer_name="SwitchManager")
|
||||
print("\n══════════════════════════════════════════════════")
|
||||
print(" ERS 5952 Switch Manager — TOTP Setup")
|
||||
print(" ERS 59100GTS-PWR+ Switch Manager — TOTP Setup")
|
||||
print("══════════════════════════════════════════════════")
|
||||
print(f"\n Manual entry secret:\n {secret}")
|
||||
print(f"\n Provisioning URI (paste into authenticator app):\n {uri}")
|
||||
@@ -217,16 +217,16 @@ def san_vid(v, field="vlan_id") -> int:
|
||||
return vid
|
||||
|
||||
def san_port(v) -> int:
|
||||
"""Validate and return a port number (1–52 for the ERS 5952)."""
|
||||
"""Validate and return a port number (1–100 for the ERS 59100GTS-PWR+)."""
|
||||
p = int(v)
|
||||
if not 1 <= p <= 52:
|
||||
raise ValueError("port: must be 1–52")
|
||||
if not 1 <= p <= 100:
|
||||
raise ValueError("port: must be 1–100")
|
||||
return p
|
||||
|
||||
_ALLOWED_CMD_RE = [
|
||||
re.compile(r'^vlan\s+(create|members|tagging|pvid)\s'),
|
||||
re.compile(r'^no\s+vlan\s+\d+$'),
|
||||
re.compile(r'^interface\s+(FastEthernet|GigabitEthernet|vlan)\s'),
|
||||
re.compile(r'^interface\s+(GigabitEthernet\s+1/\d+|vlan\s+\d+)$'),
|
||||
re.compile(r'^\s+(name|no\s+shutdown|poe|speed|duplex|ip\s+access-group|shutdown)\b'),
|
||||
re.compile(r'^hostname\s+\S+$'),
|
||||
re.compile(r'^ip\s+access-list\s+extended\s'),
|
||||
@@ -600,14 +600,14 @@ class PortConfig(BaseModel):
|
||||
|
||||
def build_port(cfg: PortConfig) -> list[str]:
|
||||
"""
|
||||
Generate ERS 5952 CLI commands for a port configuration change.
|
||||
Generate ERS 59100GTS-PWR+ CLI commands for a port configuration change.
|
||||
|
||||
Port 1–48 are FastEthernet; ports 49–52 are GigabitEthernet SFP uplinks.
|
||||
PoE is only available on ports 1–48.
|
||||
Ports 1–96 are GigabitEthernet copper (PoE capable); ports 97–100 are SFP+ uplinks (no PoE).
|
||||
All interfaces use slot/port notation: GigabitEthernet 1/{p}.
|
||||
Returns a list of CLI command strings ready for push_one_by_one().
|
||||
"""
|
||||
p = cfg.port
|
||||
iface = f"FastEthernet {p}" if p <= 48 else f"GigabitEthernet {p}"
|
||||
iface = f"GigabitEthernet 1/{p}"
|
||||
cmds = []
|
||||
if cfg.description:
|
||||
cmds += [f"interface {iface}", f' name "{cfg.description}"']
|
||||
@@ -615,15 +615,15 @@ def build_port(cfg: PortConfig) -> list[str]:
|
||||
cmds += [f"interface {iface}", " shutdown"]
|
||||
elif cfg.mode == "access":
|
||||
vid = san_vid(cfg.access_vlan, "access_vlan")
|
||||
cmds += [f"vlan members add {vid} {p}", f"vlan pvid {p} {vid}"]
|
||||
cmds += [f"vlan members add {vid} 1/{p}", f"vlan pvid 1/{p} {vid}"]
|
||||
elif cfg.mode == "trunk":
|
||||
native = san_vid(cfg.native_vlan, "native_vlan")
|
||||
tagged = [san_vid(v, f"tagged_{v}") for v in (cfg.tagged_vlans or [])]
|
||||
if tagged:
|
||||
ts = ",".join(str(v) for v in tagged)
|
||||
cmds += [f"vlan members add {ts} {p}", f"vlan tagging {ts} {p}"]
|
||||
cmds.append(f"vlan pvid {p} {native}")
|
||||
if p <= 48:
|
||||
cmds += [f"vlan members add {ts} 1/{p}", f"vlan tagging {ts} 1/{p}"]
|
||||
cmds.append(f"vlan pvid 1/{p} {native}")
|
||||
if p <= 96:
|
||||
cmds += [f"interface {iface}",
|
||||
" poe enable" if cfg.poe else " no poe enable"]
|
||||
if cfg.poe:
|
||||
@@ -632,7 +632,7 @@ def build_port(cfg: PortConfig) -> list[str]:
|
||||
|
||||
def build_acl(acl: AclCreate) -> list[str]:
|
||||
"""
|
||||
Generate ERS 5952 CLI commands to create an extended IP ACL and apply it to a VLAN interface.
|
||||
Generate ERS 59100GTS-PWR+ CLI commands to create an extended IP ACL and apply it to a VLAN interface.
|
||||
|
||||
Rules are numbered sequentially starting from 1.
|
||||
The ACL is applied to the VLAN's Layer 3 interface in the specified direction (in/out).
|
||||
@@ -657,7 +657,7 @@ def build_acl(acl: AclCreate) -> list[str]:
|
||||
# APP
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
app = FastAPI(title="ERS 5952 Switch Manager", version="3.0.0",
|
||||
app = FastAPI(title="ERS 59100GTS-PWR+ Switch Manager", version="3.0.0",
|
||||
docs_url="/api/docs", redoc_url=None)
|
||||
|
||||
app.add_middleware(CORSMiddleware, allow_origins=ALLOWED_ORIGINS,
|
||||
@@ -868,7 +868,7 @@ def _save_devices(devices: list):
|
||||
DEVICES_FILE.write_text(_json.dumps(devices, indent=2))
|
||||
|
||||
def _parse_dhcp_leases(raw: str) -> list:
|
||||
"""Parse ERS 5952 'show dhcp-server leases' output."""
|
||||
"""Parse ERS 59100GTS-PWR+ 'show dhcp-server leases' output."""
|
||||
import re
|
||||
leases = []
|
||||
for line in raw.splitlines():
|
||||
@@ -931,7 +931,7 @@ class PinholeRequest(BaseModel):
|
||||
allow: bool = True
|
||||
|
||||
def _build_dhcp_reservation_cmds(device: DeviceEntry) -> list:
|
||||
"""Generate ERS 5952 CLI for DHCP reservation (static binding)."""
|
||||
"""Generate ERS 59100GTS-PWR+ CLI for DHCP reservation (static binding)."""
|
||||
mac_clean = device.mac.replace(':','-').upper()
|
||||
return [
|
||||
f"ip dhcp-server static-binding {device.ip}",
|
||||
@@ -1317,7 +1317,7 @@ def _get_opnsense_leases(cfg: dict) -> list:
|
||||
return []
|
||||
|
||||
def _get_switch_reservations() -> list:
|
||||
"""Fetch DHCP static bindings from ERS 5952."""
|
||||
"""Fetch DHCP static bindings from ERS 59100GTS-PWR+."""
|
||||
import re as _re
|
||||
try:
|
||||
raw = read_cmd("show dhcp-server static-binding")
|
||||
@@ -1369,7 +1369,7 @@ def _get_relay_status() -> dict:
|
||||
return {"vlans": {}, "ok": False}
|
||||
|
||||
def _build_relay_cmds(opnsense_ip: str, vlan_ids: list) -> list:
|
||||
"""Generate ERS 5952 CLI to set ip helper-address on the specified VLANs."""
|
||||
"""Generate ERS 59100GTS-PWR+ CLI to set ip helper-address on the specified VLANs."""
|
||||
cmds = []
|
||||
for vid in vlan_ids:
|
||||
cmds += [
|
||||
@@ -1706,7 +1706,7 @@ def _build_ctrld_toml(vlan_profiles: list, local_domain: str = "lan",
|
||||
active = [vp for vp in vlan_profiles if vp.get("resolver_id", "").strip()]
|
||||
|
||||
lines = [
|
||||
"# ctrld configuration — generated by Avaya 5952 Switch Manager",
|
||||
"# ctrld configuration — generated by Avaya 59100GTS-PWR+ Switch Manager",
|
||||
"# Documentation: https://docs.controld.com/docs/ctrld",
|
||||
"",
|
||||
"[service]",
|
||||
@@ -2212,7 +2212,7 @@ def _generate_dnsmasq_conf(entries: list, mgmt_ip: str = "192.168.99.50") -> str
|
||||
|
||||
lines = [
|
||||
"# dnsmasq — local .lan hostname resolution",
|
||||
"# Generated by Avaya 5952 Switch Manager",
|
||||
"# Generated by Avaya 59100GTS-PWR+ Switch Manager",
|
||||
"# Listens on port 5353 (mapped from Docker container port 53)",
|
||||
"# ctrld forwards *.lan and *.local here",
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user