scm-harmonic-cmts-admin/shared.py

158 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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