159 lines
6.1 KiB
Python
159 lines
6.1 KiB
Python
# --- 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": "系統設定已更新"}
|