diff --git a/switch_backend.py b/switch_backend.py index e9fd27b..14fbcd7 100644 --- a/switch_backend.py +++ b/switch_backend.py @@ -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", "",