scm-harmonic-cmts-admin/shared.py

158 lines
5.9 KiB
Python
Raw Normal View History

# --- 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()