# --- shared.py --- import asyncio import json import os DEBUG_MODE = False def debug_print(msg: str): if DEBUG_MODE: print(msg) # 預設的 CMTS 連線樣板 CMTS_DEVICE = { 'device_type': 'cisco_ios', 'host': '10.14.110.4', 'username': 'admin', 'password': 'nsgadmin', 'port': 22, 'fast_cli': False, 'global_delay_factor': 2 } def parse_cli_to_tree(cli_text: str) -> dict: """ 將 CMTS 的 CLI 配置文字,根據縮排轉換為巢狀 JSON (Dictionary) """ # 過濾掉空行與註解 (!) lines = [line for line in cli_text.splitlines() if line.strip() and line.strip() != '!'] def parse_block(start_idx: int, base_indent: int): block = {} i = start_idx while i < len(lines): line = lines[i] indent = len(line) - len(line.lstrip()) if indent < base_indent: break content = line.strip() has_children = False if i + 1 < len(lines): next_indent = len(lines[i+1]) - len(lines[i+1].lstrip()) if next_indent > base_indent: has_children = True if has_children: child_block, next_i = parse_block(i + 1, next_indent) # 🌟 修改 1:如果區塊名稱已存在,加上帶有「空白」的虛擬索引 (例如: logging evt 1024 [1]) if content in block: idx = 1 new_key = f"{content} [{idx}]" while new_key in block: idx += 1 new_key = f"{content} [{idx}]" block[new_key] = child_block else: block[content] = child_block i = next_i else: parts = content.split(maxsplit=1) key = parts[0] val = parts[1] if len(parts) == 2 else "" # 🌟 修改 2:如果單行指令的 Key 已存在,加上帶有「空白」的虛擬索引 (例如: alias [1]) if key in block: idx = 1 new_key = f"{key} [{idx}]" while new_key in block: idx += 1 new_key = f"{key} [{idx}]" block[new_key] = val else: block[key] = val i += 1 return block, i tree, _ = parse_block(0, 0) return tree def deep_split_tree(data): """將平坦的 CLI 字典,依照空白字元拆分成深層巢狀結構 (具備終極型態衝突保護)""" if not isinstance(data, dict): return data nested_tree = {} for key, value in data.items(): processed_value = deep_split_tree(value) parts = str(key).strip().split() if not parts: continue current = nested_tree for i, part in enumerate(parts): if i == len(parts) - 1: if part not in current: current[part] = processed_value else: # 💡 終極衝突保護:安全轉型,絕不遺失資料 if isinstance(current[part], dict) and isinstance(processed_value, dict): current[part].update(processed_value) elif isinstance(current[part], dict) and not isinstance(processed_value, dict): # 原本是資料夾,新來的是字串 -> 塞入虛擬 key [0], [1] idx = 0 while f"[{idx}]" in current[part]: idx += 1 current[part][f"[{idx}]"] = processed_value elif not isinstance(current[part], dict) and isinstance(processed_value, dict): # 原本是字串,新來的是資料夾 -> 升級成資料夾,並保留原字串至 [0] old_val = current[part] current[part] = processed_value idx = 0 while f"[{idx}]" in current[part]: idx += 1 current[part][f"[{idx}]"] = old_val else: # 兩者都是字串 -> 升級成資料夾,包含兩個字串 old_val = current[part] current[part] = {"[0]": old_val, "[1]": processed_value} else: if part not in current: current[part] = {} elif not isinstance(current[part], dict): # 中間節點原本是字串,必須升級為資料夾,並保留原字串 old_val = current[part] current[part] = {"[0]": old_val} current = current[part] return nested_tree # ========================================== # 💡 系統設定管理 (System Settings Manager) # ========================================== SETTINGS_FILE = "system_settings.json" def load_settings(): """讀取系統設定,若檔案不存在則回傳預設的黑名單""" if os.path.exists(SETTINGS_FILE): try: with open(SETTINGS_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: pass # 預設隱藏這些比較雜亂且不常修改的系統層級設定 return {"hidden_keys": ["logging", "privilege", "aaa", "certificate", "line"]} def save_settings(settings_data): """將系統設定寫入 JSON 檔案""" with open(SETTINGS_FILE, "w", encoding="utf-8") as f: json.dump(settings_data, f, indent=4, ensure_ascii=False) # 全域非同步鎖:確保同一時間只有一人能執行設定寫入 cmts_config_lock = asyncio.Lock()