# --- routers/config.py --- import re from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List from netmiko import ConnectHandler # 💡 確保從 shared 引入 deep_split_tree from shared import CMTS_DEVICE, cmts_config_lock, parse_cli_to_tree, deep_split_tree, load_settings, save_settings router = APIRouter() class ConfigRequest(BaseModel): script: str host: str username: str password: str @router.post("/cmts-config") async def execute_cmts_config(req: ConfigRequest): # 使用非同步鎖,確保同一時間只有一個寫入任務執行 async with cmts_config_lock: try: device = CMTS_DEVICE.copy() device.update({'host': req.host, 'username': req.username, 'password': req.password}) # 過濾掉空行與註解 (! 開頭的行) commands = [cmd.strip() for cmd in req.script.splitlines() if cmd.strip() and not cmd.strip().startswith('!')] net_connect = ConnectHandler(**device) net_connect.send_command_timing("config") # 批次送出設定指令 output = net_connect.send_config_set(commands, cmd_verify=False) net_connect.send_command_timing("exit") net_connect.disconnect() return {"status": "success", "data": output} except Exception as e: raise HTTPException(status_code=500, detail=f"CMTS Config Error: {str(e)}") # ========================================== # 💡 以下為 DS RF Port 動態樹狀查詢 API # ========================================== @router.get("/cmts-ds-rf-port-list") async def get_ds_rf_port_list(host: str, username: str, password: str = ""): """獲取設備上所有的 DS RF Port 清單""" try: device = CMTS_DEVICE.copy() device.update({'host': host, 'username': username, 'password': password}) net_connect = ConnectHandler(**device) # 使用 include 只抓取關鍵字,並把等待時間延長至 60 秒 command = 'show running-config | include "cable ds-rf-port"' raw_output = net_connect.send_command(command, read_timeout=60) net_connect.disconnect() # 使用正規表達式擷取 port 號碼 (例如 62:0/0) pattern = r"cable ds-rf-port\s+([0-9:/]+)" ports = re.findall(pattern, raw_output) unique_ports = sorted(list(set(ports))) return { "status": "success", "data": unique_ports, "raw_output": raw_output } except Exception as e: return {"status": "error", "message": str(e)} @router.get("/cmts-ds-rf-port-config") async def get_ds_rf_port_config(target: str, host: str, username: str, password: str = ""): """獲取特定 DS RF Port 的配置,並轉換為樹狀 JSON""" try: device = CMTS_DEVICE.copy() device.update({'host': host, 'username': username, 'password': password}) net_connect = ConnectHandler(**device) command = f"show running-config interface cable ds-rf-port {target} | nomore" raw_output = net_connect.send_command(command, read_timeout=60) net_connect.disconnect() # 1. 初步解析縮排 base_tree = parse_cli_to_tree(raw_output) # 2. 💡 深層解析空白字元,建立完美樹狀結構 perfect_tree = deep_split_tree(base_tree) return { "status": "success", "data": perfect_tree, # 回傳完美的樹狀結構 "raw_cli": raw_output.strip() } except Exception as e: return {"status": "error", "message": str(e)} @router.get("/cmts-full-config") async def get_full_config(host: str, username: str, password: str = "", skip_filter: bool = False): """獲取整台 CMTS 的完整配置,並轉換為大型樹狀 JSON""" try: device = CMTS_DEVICE.copy() device.update({'host': host, 'username': username, 'password': password}) net_connect = ConnectHandler(**device) command = "show running-config | nomore" raw_output = net_connect.send_command(command, read_timeout=60) net_connect.disconnect() base_tree = parse_cli_to_tree(raw_output) perfect_tree = deep_split_tree(base_tree) # ========================================== # 💡 深度過濾攔截器:支援多層級路徑 (如 cable.mac-domain) # ========================================== if not skip_filter: settings = load_settings() hidden_keys = settings.get("hidden_keys", []) for path in hidden_keys: keys = path.split('.') current = perfect_tree # 走到倒數第二層 for k in keys[:-1]: if isinstance(current, dict) and k in current: current = current[k] else: current = None break # 刪除最後一層的目標節點 if isinstance(current, dict) and keys[-1] in current: current.pop(keys[-1], None) # ========================================== return {"status": "success", "data": perfect_tree} except Exception as e: return {"status": "error", "message": str(e)} # ========================================== # 💡 系統設定 API (System Settings) # ========================================== class SettingsRequest(BaseModel): hidden_keys: list[str] @router.get("/settings/tree-filters") async def get_tree_filters(): """獲取目前的樹狀圖隱藏名單""" settings = load_settings() return {"status": "success", "data": settings["hidden_keys"]} @router.post("/settings/tree-filters") async def update_tree_filters(req: SettingsRequest): """更新樹狀圖隱藏名單並存檔""" save_settings({"hidden_keys": req.hidden_keys}) return {"status": "success", "message": "系統設定已更新"}