fix: 讓樹狀結構符合Harmonic設定檔的結構

This commit is contained in:
swpa 2026-05-11 18:16:03 +08:00
parent ad22331776
commit d57a01f05b
4 changed files with 583 additions and 545 deletions

View File

@ -98704,5 +98704,329 @@
"type": "string_or_number", "type": "string_or_number",
"current_value": null, "current_value": null,
"updated_at": 1778235685 "updated_at": 1778235685
},
"ssh": {
"hint": "Possible completions:\n[enabled] disabled enabled",
"options": [
"disabled",
"enabled"
],
"type": "list",
"current_value": "enabled",
"updated_at": 1778469843
},
"hostname": {
"hint": "Possible completions:\n<string, min: 0 chars, max: 32 chars>[SERCOMM-COS-02]",
"options": [],
"type": "string_or_number",
"current_value": "SERCOMM-COS-02",
"updated_at": 1778469880
},
"cable mac-domain 13:0/0.0 map-advance extra-usec": {
"hint": "Description: Map Advance Algorithm Delay\nPossible completions:\n<unsignedShort, 0 .. 20000>[100]",
"options": [],
"type": "string_or_number",
"current_value": "100",
"updated_at": 1778482045
},
"cable mac-domain 13:0/0.0 map-advance static": {
"hint": "Possible completions:\nalgorithm Dynamic map-advance algorithm sub-menu\nextra-usec Map Advance Algorithm Delay\nmeasurements Enable/Disable latency measurement\n<cr>",
"options": [],
"type": "string_or_number",
"current_value": null,
"updated_at": 1778482833
},
"cable bundle 1 dhcp-relay ipv4 cable-relay-agent-ip-address": {
"hint": "Description: giaddress of external relay agent\nPossible completions:\n<IPv4 address>[10.14.31.1]",
"options": [],
"type": "string_or_number",
"current_value": "10.14.31.1",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv4 network-relay-agent-ip-address": {
"hint": "Description: IPv4 address of external relay agent for dhcp-server side interface\nPossible completions:\n<IPv4 address>[10.14.23.5]",
"options": [],
"type": "string_or_number",
"current_value": "10.14.23.5",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv4 dhcp-server-ip-address": {
"hint": "Description: IPv4 addresses list of relay's DHCP-server\nPossible completions:\nIPv4 address",
"options": [],
"type": "string_or_number",
"current_value": null,
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv4 dhcp-server-gw-ip-address": {
"hint": "Description: IPv4 address of default route for dhcp-server side interface\nPossible completions:\n<IPv4address/netmask>[10.14.23.1/24]",
"options": [],
"type": "string_or_number",
"current_value": "10.14.23.1/24",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv4 network-relay-agent-vlan-id": {
"hint": "Description: VLAN id for dhcp-server side interface connection. 0 for untagged\nPossible completions:\n<unsignedShort, 0 .. 4095>[110]",
"options": [],
"type": "string_or_number",
"current_value": "110",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv4 cable-relay-agent-nsi-ip-address": {
"hint": "Description: IPv4 address of external relay agent for clients connections side interface\nPossible completions:\n<IPv4address/netmask>[10.14.11.4/24]",
"options": [],
"type": "string_or_number",
"current_value": "10.14.11.4/24",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv6 cable-relay-agent-ip-address": {
"hint": "Description: IPv6 address of external relay agent for clients connections side interface\nPossible completions:\n<IPv6 address>[2001:50:5:31::1]",
"options": [],
"type": "string_or_number",
"current_value": "2001:50:5:31::1",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv6 network-relay-agent-ip-address": {
"hint": "Description: IPv6 address of external relay agent for dhcp-server side interface\nPossible completions:\n<IPv6 address>[2001:50:5:23::5]",
"options": [],
"type": "string_or_number",
"current_value": "2001:50:5:23::5",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv6 dhcp-server-ip-address": {
"hint": "Description: IPv6 addresses list of relay's DHCP-server\nPossible completions:\nIPv6 address",
"options": [],
"type": "string_or_number",
"current_value": null,
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv6 dhcp-server-gw-ip-address": {
"hint": "Description: IPv6 address of default route for dhcp-server side interface\nPossible completions:\n<IPv6address/prefix>[2001:50:5:23::1/64]",
"options": [],
"type": "string_or_number",
"current_value": "2001:50:5:23::1/64",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv6 network-relay-agent-vlan-id": {
"hint": "Description: VLAN id for dhcp-server side interface connection. 0 for untagged\nPossible completions:\n<unsignedShort, 0 .. 4095>[110]",
"options": [],
"type": "string_or_number",
"current_value": "110",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-relay ipv6 cable-relay-agent-nsi-ip-address": {
"hint": "Description: IPv6 address of external relay agent for clients connections side interface\nPossible completions:\n<IPv6address/netmask>[2001:50:5:11::4/64]",
"options": [],
"type": "string_or_number",
"current_value": "2001:50:5:11::4/64",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-options v4 dhcp-option-82 format": {
"hint": "Description: DHCPv6 option 82 output format\nPossible completions:\n[bin] ascii bin",
"options": [
"ascii",
"bin"
],
"type": "list",
"current_value": "bin",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-options v4 dhcp-option-82 hostname": {
"hint": "Description: Set DHCPv4 option 82 hostname\nPossible completions:\n[disabled] disabled enabled",
"options": [
"disabled",
"enabled"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-options v4 dhcp-option-82 upstream-description": {
"hint": "Description: Set DHCPv4 option 82 upstream-description\nPossible completions:\n[disabled] disabled enabled",
"options": [
"disabled",
"enabled"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-options v4 dhcp-option-82 us-service-class-name": {
"hint": "Description: Set DHCPv4 82.9.5 us-service-class name sub-option\nPossible completions:\n[disabled] disabled enabled",
"options": [
"disabled",
"enabled"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-options v4 dhcp-option-82 compatibility": {
"hint": "Possible completions:",
"options": [],
"type": "unknown",
"current_value": null,
"updated_at": 1778486389
},
"cable bundle 1 dhcp-options v6 dhcp-option-17 hostname": {
"hint": "Description: Set DHCPv6 option 17 hostname\nPossible completions:\n[disabled] disabled enabled",
"options": [
"disabled",
"enabled"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 dhcp-options v6 dhcp-option-17 upstream-description": {
"hint": "Description: Set DHCPv6 option 17 upstream-description\nPossible completions:\n[disabled] disabled enabled",
"options": [
"disabled",
"enabled"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 source-verify dhcp-server": {
"hint": "Description: Configure a list of DHCP server ip addresses which will be used for DHCP LQ\nPossible completions:\nIP address",
"options": [],
"type": "string_or_number",
"current_value": null,
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery throttling-rate": {
"hint": "Description: Leasequery throttling rate (requests per second)\nPossible completions:\n<unsignedInt, 1 .. 32767>[500]",
"options": [],
"type": "string_or_number",
"current_value": "500",
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery downstream": {
"hint": "Description: Enable/disable DHCP LQ on DS for IPv4\nPossible completions:\n[disabled] disabled enabled requests-per-sec",
"options": [
"disabled",
"enabled",
"requests-per-sec"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery downstream requests-per-sec": {
"hint": "Possible completions:\n<unsignedInt, 1 .. 100>[5]",
"options": [],
"type": "string_or_number",
"current_value": "5",
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery upstream": {
"hint": "Description: Enable/disable DHCP LQ on US for IPv4\nPossible completions:\n[disabled] disabled enabled requests-per-sec",
"options": [
"disabled",
"enabled",
"requests-per-sec"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery upstream requests-per-sec": {
"hint": "Possible completions:\n<unsignedInt, 1 .. 100>[5]",
"options": [],
"type": "string_or_number",
"current_value": "5",
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery ipv6 downstream": {
"hint": "Description: Enable/disable DHCP LQ on DS for IPv6\nPossible completions:\n[disabled] disabled enabled requests-per-sec",
"options": [
"disabled",
"enabled",
"requests-per-sec"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery ipv6 downstream requests-per-sec": {
"hint": "Possible completions:\n<unsignedInt, 1 .. 100>[5]",
"options": [],
"type": "string_or_number",
"current_value": "5",
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery ipv6 upstream": {
"hint": "Description: Enable/disable DHCP LQ on US for IPv6\nPossible completions:\n[disabled] disabled enabled requests-per-sec",
"options": [
"disabled",
"enabled",
"requests-per-sec"
],
"type": "list",
"current_value": "disabled",
"updated_at": 1778486389
},
"cable bundle 1 source-verify leasequery ipv6 upstream requests-per-sec": {
"hint": "Possible completions:\n<unsignedInt, 1 .. 100>[5]",
"options": [],
"type": "string_or_number",
"current_value": "5",
"updated_at": 1778486389
},
"cable bundle 1 nsi start-ipv4-address": {
"hint": "Description: Starting address within ipv4 subnet on the CRE for the bundle nsi subnet\nPossible completions:\n<IPv4 address>[10.14.11.5]",
"options": [],
"type": "string_or_number",
"current_value": "10.14.11.5",
"updated_at": 1778486389
},
"alias ccm": {
"hint": "Description: Original command syntax. Valid abbreviations of the original\ncommand syntax can be entered for the command-syntax\nargument.\nPossible completions:\n<string>[clear cable modem] <cr>",
"options": [],
"type": "string_or_number",
"current_value": "clear cable modem",
"updated_at": 1778486476
},
"alias dcm": {
"hint": "Description: Original command syntax. Valid abbreviations of the original\ncommand syntax can be entered for the command-syntax\nargument.\nPossible completions:\n<string>[debug cable modem] <cr>",
"options": [],
"type": "string_or_number",
"current_value": "debug cable modem",
"updated_at": 1778486476
},
"alias dcr": {
"hint": "Description: Original command syntax. Valid abbreviations of the original\ncommand syntax can be entered for the command-syntax\nargument.\nPossible completions:\n<string>[debug cable rpd] <cr>",
"options": [],
"type": "string_or_number",
"current_value": "debug cable rpd",
"updated_at": 1778486476
},
"alias dcs": {
"hint": "Description: Original command syntax. Valid abbreviations of the original\ncommand syntax can be entered for the command-syntax\nargument.\nPossible completions:\n<string>[debug cluster server] <cr>",
"options": [],
"type": "string_or_number",
"current_value": "debug cluster server",
"updated_at": 1778486476
},
"alias scr": {
"hint": "Description: Original command syntax. Valid abbreviations of the original\ncommand syntax can be entered for the command-syntax\nargument.\nPossible completions:\n<string>[show cable rpd] <cr>",
"options": [],
"type": "string_or_number",
"current_value": "show cable rpd",
"updated_at": 1778486476
},
"alias scs": {
"hint": "Description: Original command syntax. Valid abbreviations of the original\ncommand syntax can be entered for the command-syntax\nargument.\nPossible completions:\n<string>[show cluster server] <cr>",
"options": [],
"type": "string_or_number",
"current_value": "show cluster server",
"updated_at": 1778486476
},
"no": {
"hint": "Possible completions:\naaa COS AAA\nalias Create command alias.\nbanner Customize text messagees (banners) which are to be displayed before login/pass prompt\ncli Global CLI configuration\nclock\nhostname Hostname for CableOS Server Cluster\ninterface Configuration of PON and Ethernet networks\nipdr\nlogging Logging sub configuration\nnetwork\nprivilege\nradius-server\nsnmp\nsnmp-server\nspanning-tree\nssh Configure SSH access to CLI\nsystem\ntacacs-server\nusername Establish a username-based authentication system\n---\nservice Modify use of network based services\n\u001b[7m--More--\u001b[27m\b\b\b\b\b\b\b\b \b\b\b\b\b\b\b\b <cr>",
"options": [],
"type": "string_or_number",
"current_value": null,
"updated_at": 1778489033
} }
} }

182
shared.py
View File

@ -2,6 +2,8 @@
import asyncio import asyncio
import json import json
import os import os
import re
from collections import defaultdict
DEBUG_MODE = False DEBUG_MODE = False
@ -22,66 +24,156 @@ CMTS_DEVICE = {
def parse_cli_to_tree(cli_text: str) -> dict: def parse_cli_to_tree(cli_text: str) -> dict:
""" """
CMTS CLI 配置文字根據縮排轉換為巢狀 JSON (Dictionary) CMTS CLI 配置文字轉換為完美的邏輯巢狀 JSON (兩階段解析法)
""" """
# 過濾掉空行與註解 (!) lines = [line.rstrip() for line in cli_text.splitlines() if line.strip()]
lines = [line for line in cli_text.splitlines() if line.strip() and line.strip() != '!']
# ==========================================
def parse_block(start_idx: int, base_indent: int): # 階段 1建立實體樹 (依據縮排與 '!')
block = {} # ==========================================
def build_raw_tree(start_idx, current_indent):
nodes = []
i = start_idx i = start_idx
while i < len(lines): while i < len(lines):
line = lines[i] line = lines[i]
stripped = line.strip()
# 遇到 ! 的處理邏輯
if stripped == '!':
if current_indent > 0:
return nodes, i + 1 # 子區塊遇到 !,結束並消耗該行
else:
i += 1 # Root 層級的 ! 只是分隔符號,直接跳過
continue
indent = len(line) - len(line.lstrip()) indent = len(line) - len(line.lstrip())
if indent < base_indent: # 縮排退回,隱含結束當前區塊 (不消耗該行)
break if indent < current_indent:
return nodes, i
content = line.strip()
# 探測是否有子節點
has_children = False has_children = False
if i + 1 < len(lines): if i + 1 < len(lines):
next_indent = len(lines[i+1]) - len(lines[i+1].lstrip()) next_line = lines[i+1]
if next_indent > base_indent: next_stripped = next_line.strip()
has_children = True if next_stripped != '!':
next_indent = len(next_line) - len(next_line.lstrip())
if next_indent > indent:
has_children = True
if has_children: if has_children:
child_block, next_i = parse_block(i + 1, next_indent) children, next_i = build_raw_tree(i + 1, next_indent)
nodes.append({"type": "folder", "content": stripped, "children": children})
# 🌟 修改 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 i = next_i
else: else:
parts = content.split(maxsplit=1) nodes.append({"type": "leaf", "content": stripped})
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 i += 1
return nodes, i
return block, i
tree, _ = parse_block(0, 0) raw_tree, _ = build_raw_tree(0, 0)
return tree
# ==========================================
# 階段 2邏輯群組化與字典合併
# ==========================================
def deep_merge(dict1, dict2):
"""深度合併兩個字典 (解決同名資料夾與指令群組的融合)"""
for k, v in dict2.items():
if k in dict1:
if isinstance(dict1[k], dict) and isinstance(v, dict):
deep_merge(dict1[k], v)
else:
# 發生碰撞時的安全機制
idx = 1
while f"{k} [{idx}]" in dict1:
idx += 1
dict1[f"{k} [{idx}]"] = v
else:
dict1[k] = v
def nest_folder_key(key_string, value_dict, target_dict):
"""'cable mac-domain 13' 拆解為巢狀字典"""
parts = key_string.split()
current = target_dict
for i, part in enumerate(parts):
if i == len(parts) - 1:
if part not in current:
current[part] = value_dict
else:
if isinstance(current[part], dict) and isinstance(value_dict, dict):
deep_merge(current[part], value_dict)
else:
current[f"{part} (資料夾)"] = value_dict
else:
if part not in current or not isinstance(current[part], dict):
current[part] = {}
current = current[part]
def group_leaves(leaf_strings):
"""將單行指令依據共同前綴進行遞迴群組化"""
grouped = {}
first_word_map = defaultdict(list)
for s in leaf_strings:
parts = s.split(maxsplit=1)
first_word = parts[0]
remainder = parts[1] if len(parts) > 1 else ""
first_word_map[first_word].append(remainder)
for prefix, remainders in first_word_map.items():
if len(remainders) == 1:
grouped[prefix] = remainders[0]
else:
valid_remainders = [r for r in remainders if r]
empty_count = len(remainders) - len(valid_remainders)
group_dict = {}
if valid_remainders:
sub_grouped = group_leaves(valid_remainders)
# 判斷是否全為純陣列值 (例如 IP 列表)
is_all_empty_vals = all(not isinstance(v, dict) and v == "" for v in sub_grouped.values())
if is_all_empty_vals:
for i, k in enumerate(sub_grouped.keys()):
group_dict[f"[{i}]"] = k
else:
idx = 0
for k, v in sub_grouped.items():
if isinstance(v, dict):
group_dict[k] = v
elif v == "":
group_dict[f"[{idx}]"] = k
idx += 1
else:
group_dict[k] = v
# 補回完全沒有後續參數的指令
for _ in range(empty_count):
idx = 0
while f"[{idx}]" in group_dict:
idx += 1
group_dict[f"[{idx}]"] = ""
grouped[prefix] = group_dict
return grouped
def fold_nodes(nodes):
result = {}
# 1. 處理資料夾 (Folders)
for node in [n for n in nodes if n["type"] == "folder"]:
folded_children = fold_nodes(node["children"])
nest_folder_key(node["content"], folded_children, result)
# 2. 處理單行指令 (Leaves)
leaves = [n["content"] for n in nodes if n["type"] == "leaf"]
if leaves:
leaf_dict = group_leaves(leaves)
deep_merge(result, leaf_dict)
return result
return fold_nodes(raw_tree)
def deep_split_tree(data): def deep_split_tree(data):
"""將平坦的 CLI 字典,依照空白字元拆分成深層巢狀結構 (具備終極型態衝突保護)""" """將平坦的 CLI 字典,依照空白字元拆分成深層巢狀結構 (具備終極型態衝突保護)"""

View File

@ -89,6 +89,28 @@ function resetIdleTimer() {
window.addEventListener('mousemove', resetIdleTimer); window.addEventListener('mousemove', resetIdleTimer);
window.addEventListener('keydown', resetIdleTimer); window.addEventListener('keydown', resetIdleTimer);
window.addEventListener('click', resetIdleTimer); window.addEventListener('click', resetIdleTimer);
// ==========================================
// 🌟 輔助函數:全部展開與全部收合
// ==========================================
function expandAll(elementId) {
const details = document.getElementById(`details-${elementId}`);
if (details) {
details.open = true; // 展開自己
const subDetails = details.querySelectorAll('details');
subDetails.forEach(d => d.open = true); // 展開所有子項目
}
}
function collapseAll(elementId) {
const details = document.getElementById(`details-${elementId}`);
if (details) {
const subDetails = details.querySelectorAll('details');
subDetails.forEach(d => d.open = false); // 收合所有子項目
details.open = false; // 收合自己
}
}
// 🎨 輔助函數:解析終端機輸出,僅將錯誤/警告行標紅 // 🎨 輔助函數:解析終端機輸出,僅將錯誤/警告行標紅
function highlightTerminalOutput(text) { function highlightTerminalOutput(text) {
if (!text) return ''; if (!text) return '';
@ -1514,77 +1536,114 @@ async function executeBondingConfig() {
} }
// ========================================== // ==========================================
// 💡 6. 完整設備配置樹狀圖邏輯 (支援資料夾層級鎖定) // 💡 6. 完整設備配置樹狀圖邏輯 (支援資料夾層級鎖定與虛擬群組視覺優化)
// ========================================== // ==========================================
function buildTree(data, parentPath = "") { function buildTree(node, path = '') {
if (typeof data !== 'object' || data === null) { let html = '';
return `<span>${data}</span>`; if (!node || typeof node !== 'object') return html;
}
let html = '<div style="margin-left: 20px;">'; for (const [key, value] of Object.entries(node)) {
for (const key in data) { const currentPath = path ? `${path}::${key}` : key;
const value = data[key];
const currentPath = parentPath ? `${parentPath}::${key}` : key; // 🌟 產生純淨的 CLI 指令路徑 (替換 :: 為空白,並移除虛擬索引)
const cliCommand = currentPath.replace(/::/g, ' ').replace(/\s*\[\d+\]/g, '');
if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) { if (typeof value === 'object' && value !== null) {
// 📁 渲染資料夾 (加入編輯與操作按鈕) // 🌟 處理資料夾或指令群組
const elementId = `folder-${currentPath.replace(/[^a-zA-Z0-9]/g, '-')}`; const elementId = `folder-${currentPath.replace(/[^a-zA-Z0-9]/g, '-')}`;
const hasNestedObject = Object.values(value).some(v => typeof v === 'object' && v !== null);
const folderIcon = hasNestedObject ? '📁' : '📑';
const folderSuffix = hasNestedObject ? '' : '<span style="color: #95a5a6; font-size: 0.85em; font-weight: normal; margin-left: 5px;">(指令群組)</span>';
// 🌟 定義專業的 SVG 向量圖示
const expandAllIcon = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="7 13 12 18 17 13"></polyline><polyline points="7 6 12 11 17 6"></polyline></svg>`;
const collapseAllIcon = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="17 11 12 6 7 11"></polyline><polyline points="17 18 12 13 7 18"></polyline></svg>`;
// 🌟 全部展開/收合按鈕 (使用 SVG 並加入懸停變色效果)
const expandCollapseBtns = hasNestedObject ? `
<span style="margin-left: 8px; display: inline-flex; gap: 6px; align-items: center;">
<span onclick="event.stopPropagation(); event.preventDefault(); expandAll('${elementId}')"
style="cursor: pointer; color: #95a5a6; transition: color 0.2s; display: flex; align-items: center;"
onmouseover="this.style.color='#3498db'" onmouseout="this.style.color='#95a5a6'" title="展開全部子項目">
${expandAllIcon}
</span>
<span onclick="event.stopPropagation(); event.preventDefault(); collapseAll('${elementId}')"
style="cursor: pointer; color: #95a5a6; transition: color 0.2s; display: flex; align-items: center;"
onmouseover="this.style.color='#e67e22'" onmouseout="this.style.color='#95a5a6'" title="收合全部子項目">
${collapseAllIcon}
</span>
</span>
` : '';
// ✅ 加入 title="${cliCommand}" 提供懸停提示
html += ` html += `
<details id="details-${elementId}" ontoggle="this.querySelector('summary').classList.toggle('is-open', this.open)"> <details id="details-${elementId}" class="tree-folder" style="margin-top: 4px; margin-left: 20px;">
<summary class="tree-folder-header"
<!-- 💡 確保 summary flex 容器 --> title="${cliCommand}"
<summary class="tree-node-header" style="font-weight: bold; color: #2c3e50; margin-top: 5px; list-style: none; display: flex; align-items: center; padding-right: 10px;"> onmouseover="this.style.backgroundColor='#f1f2f6'"
onmouseout="this.style.backgroundColor='transparent'"
style="cursor: pointer; padding: 4px 8px; display: flex; align-items: center; outline: none; border-radius: 4px; transition: background-color 0.2s;">
<span style="margin-right: 5px;">${folderIcon}</span>
<b style="color: #2c3e50;">${key}</b>${folderSuffix}
${expandCollapseBtns}
<span class="tree-chevron"></span>
<span class="tree-folder-icon"></span>
<span>${key}</span>
<!-- 💡 關鍵 margin-left: auto 將按鈕區塊推到最右側 -->
<div style="margin-left: auto; display: flex; align-items: center;"> <div style="margin-left: auto; display: flex; align-items: center;">
<!-- 資料夾專屬編輯按鈕 -->
<span id="edit-btn-${elementId}" onclick="event.stopPropagation(); event.preventDefault(); startEditFolder('${currentPath}', '${elementId}')" <span id="edit-btn-${elementId}" onclick="event.stopPropagation(); event.preventDefault(); startEditFolder('${currentPath}', '${elementId}')"
style="cursor: pointer; font-size: 14px; opacity: 0.3; transition: opacity 0.2s;" style="cursor: pointer; font-size: 14px; opacity: 0.3; transition: opacity 0.2s;"
onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.3" title="鎖定並編輯此區塊"> onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.3" title="鎖定並編輯此群組">
</span> </span>
<!-- 儲存與取消按鈕 (預設隱藏) -->
<span id="actions-${elementId}" style="display:none; margin-left: 10px;"> <span id="actions-${elementId}" style="display:none; margin-left: 10px;">
<button onclick="event.stopPropagation(); event.preventDefault(); previewFolderCLI('${currentPath}', '${elementId}')" style="background-color: #27ae60; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px;"> 預覽指令</button> <button onclick="event.stopPropagation(); event.preventDefault(); previewFolderCLI('${currentPath}', '${elementId}')" style="background-color: #27ae60; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px;"> 預覽指令</button>
<button onclick="event.stopPropagation(); event.preventDefault(); cancelEditFolder('${currentPath}', '${elementId}')" style="background-color: #e74c3c; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; margin-left: 5px;"> 取消</button> <button onclick="event.stopPropagation(); event.preventDefault(); cancelEditFolder('${currentPath}', '${elementId}')" style="background-color: #e74c3c; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; margin-left: 5px;"> 取消</button>
</span> </span>
</div> </div>
</summary> </summary>
<div id="content-${elementId}" class="tree-folder-content" style="margin-left: 12px; border-left: 1px dashed #bdc3c7; padding-left: 12px;">
<div id="content-${elementId}"> ${buildTree(value, currentPath)}
${buildTree(value, currentPath)}
</div> </div>
</details> </details>
`; `;
} else { } else {
// 📄 渲染單一設定項目 (加入獨立編輯按鈕) // 🌟 處理單一設定項目 (Leaf)
const isVirtualIndex = /^\[\d+\]$/.test(key);
const safeValue = (value === null ? '' : value).toString().replace(/"/g, "&quot;"); const safeValue = (value === null ? '' : value).toString().replace(/"/g, "&quot;");
const elementId = `leaf-${currentPath.replace(/[^a-zA-Z0-9]/g, '-')}`; const elementId = `leaf-${currentPath.replace(/[^a-zA-Z0-9]/g, '-')}`;
html += ` let displayContent = '';
<!-- 💡 加上 tree-leaf-node 類別啟用 Hover 高光效果 -->
<div class="tree-leaf-node" style="padding: 4px 10px 4px 0; color: #34495e; margin-left: 20px; border-left: 1px solid #ecf0f1; padding-left: 10px; display: flex; align-items: center;"> if (isVirtualIndex) {
<span style="margin-right: 5px;">📄</span> <b>${key}</b>: displayContent = `
<span style="margin-right: 5px;">📄</span>
<span class="leaf-container" id="container-${elementId}" data-path="${currentPath}" data-original="${safeValue}" style="display: inline-flex; align-items: center; min-height: 24px;"> <span class="leaf-container" id="container-${elementId}" data-path="${currentPath}" data-original="${safeValue}" style="display: inline-flex; align-items: center; min-height: 24px;">
<span class="leaf-value" style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace;">${value === null ? '' : value}</span> <span class="leaf-value" style="color: #16a085; font-weight: normal; font-family: Courier New, monospace;">${safeValue}</span>
</span> </span>
`;
} else {
displayContent = `
<span style="margin-right: 5px;">📄</span> <b>${key}</b>:
<span class="leaf-container" id="container-${elementId}" data-path="${currentPath}" data-original="${safeValue}" style="display: inline-flex; align-items: center; min-height: 24px;">
<span class="leaf-value" style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace;">${safeValue}</span>
</span>
`;
}
// ✅ 加入 title="${cliCommand}" 提供懸停提示
html += `
<div class="tree-leaf-node"
title="${cliCommand}"
onmouseover="this.style.backgroundColor='#f1f2f6'"
onmouseout="this.style.backgroundColor='transparent'"
style="padding: 4px 8px; color: #34495e; margin-left: 20px; border-left: 1px solid #ecf0f1; display: flex; align-items: center; border-radius: 4px; transition: background-color 0.2s;">
${displayContent}
<div style="margin-left: auto; display: flex; align-items: center;"> <div style="margin-left: auto; display: flex; align-items: center;">
<!-- 單一項目專屬編輯按鈕 -->
<span id="edit-btn-${elementId}" onclick="event.stopPropagation(); event.preventDefault(); startEditLeaf('${currentPath}', '${elementId}')" <span id="edit-btn-${elementId}" onclick="event.stopPropagation(); event.preventDefault(); startEditLeaf('${currentPath}', '${elementId}')"
style="cursor: pointer; font-size: 14px; opacity: 0.3; transition: opacity 0.2s;" style="cursor: pointer; font-size: 14px; opacity: 0.3; transition: opacity 0.2s;"
onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.3" title="鎖定並編輯此項目"> onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.3" title="鎖定並編輯此項目">
</span> </span>
<!-- 儲存與取消按鈕 (預設隱藏) -->
<span id="actions-${elementId}" style="display:none; margin-left: 10px;"> <span id="actions-${elementId}" style="display:none; margin-left: 10px;">
<button onclick="event.stopPropagation(); event.preventDefault(); previewLeafCLI('${currentPath}', '${elementId}')" style="background-color: #27ae60; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px;"> 預覽指令</button> <button onclick="event.stopPropagation(); event.preventDefault(); previewLeafCLI('${currentPath}', '${elementId}')" style="background-color: #27ae60; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px;"> 預覽指令</button>
<button onclick="event.stopPropagation(); event.preventDefault(); cancelEditLeaf('${currentPath}', '${elementId}')" style="background-color: #e74c3c; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; margin-left: 5px;"> 取消</button> <button onclick="event.stopPropagation(); event.preventDefault(); cancelEditLeaf('${currentPath}', '${elementId}')" style="background-color: #e74c3c; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; margin-left: 5px;"> 取消</button>
@ -1594,7 +1653,6 @@ function buildTree(data, parentPath = "") {
`; `;
} }
} }
html += '</div>';
return html; return html;
} }
@ -1696,31 +1754,45 @@ function buildRealFilterTree(data, parentPath, hiddenKeys) {
const isChecked = hiddenKeys.includes(currentPath) ? "checked" : ""; const isChecked = hiddenKeys.includes(currentPath) ? "checked" : "";
if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) { if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) {
// 🌟 系統設定區塊也套用相同的視覺優化
const isVirtualGroup = Object.keys(value).every(k => /^\[\d+\]$/.test(k));
const isVirtualIndex = /^\[\d+\]$/.test(key);
let displayName = key;
let iconHtml = `<span class="tree-folder-icon"></span>`;
if (isVirtualGroup) {
iconHtml = `<span style="margin-right: 5px; font-size: 16px;">📑</span>`;
displayName = `${key} <span style="font-size: 12px; color: #7f8c8d; font-weight: normal;">(指令群組)</span>`;
} else if (isVirtualIndex) {
iconHtml = `<span style="margin-right: 5px; font-size: 14px;">📦</span>`;
displayName = `<span style="color: #95a5a6; font-weight: normal;">項目 ${key}</span>`;
}
html += ` html += `
<details ontoggle="this.querySelector('summary').classList.toggle('is-open', this.open)"> <details ontoggle="this.querySelector('summary').classList.toggle('is-open', this.open)">
<summary class="tree-node-header" style="font-weight: bold; color: #2c3e50; margin-top: 5px; list-style: none;"> <summary class="tree-node-header" style="font-weight: bold; color: #2c3e50; margin-top: 5px; list-style: none; display: flex; align-items: center;">
<!-- 💡 加上 onclick 阻擋事件冒泡避免點擊 Checkbox 時觸發資料夾展開/收合 -->
<input type="checkbox" value="${currentPath}" class="filter-checkbox" ${isChecked} <input type="checkbox" value="${currentPath}" class="filter-checkbox" ${isChecked}
onclick="event.stopPropagation();" onchange="toggleChildCheckboxes(this)"> onclick="event.stopPropagation();" onchange="toggleChildCheckboxes(this)" style="margin-right: 8px;">
<span class="tree-chevron"></span> <span class="tree-chevron"></span>
<span class="tree-folder-icon"></span> ${iconHtml}
<span style="flex-grow: 1;">${key}</span> <span style="flex-grow: 1;">${displayName}</span>
</summary> </summary>
<!-- 💡 加入特定的 class 方便後續 JS 抓取子節點 -->
<div class="filter-children-container"> <div class="filter-children-container">
${buildRealFilterTree(value, currentPath, hiddenKeys)} ${buildRealFilterTree(value, currentPath, hiddenKeys)}
</div> </div>
</details> </details>
`; `;
} else { } else {
const isVirtualIndex = /^\[\d+\]$/.test(key);
let displayName = isVirtualIndex ? `<span style="color: #95a5a6; font-weight: normal;">項目 ${key}</span>` : `<b>${key}</b>`;
const safeValue = (value === null ? '' : value).toString().replace(/"/g, "&quot;"); const safeValue = (value === null ? '' : value).toString().replace(/"/g, "&quot;");
html += ` html += `
<div style="padding: 4px 0; color: #34495e; margin-left: 20px; border-left: 1px solid #ecf0f1; padding-left: 10px; display: flex; align-items: center;"> <div style="padding: 4px 0; color: #34495e; margin-left: 20px; border-left: 1px solid #ecf0f1; padding-left: 10px; display: flex; align-items: center;">
<input type="checkbox" value="${currentPath}" class="filter-checkbox" ${isChecked} <input type="checkbox" value="${currentPath}" class="filter-checkbox" ${isChecked}
onchange="updateParentCheckboxState(this)"> onchange="updateParentCheckboxState(this)" style="margin-right: 8px;">
<span style="margin-right: 5px;">📄</span> <b>${key}</b>: <span style="margin-right: 5px;">📄</span> ${displayName}:
<span style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace;">${safeValue}</span> <span style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace;">${safeValue}</span>
</div> </div>
`; `;

View File

@ -1,453 +1,3 @@
{ {
"hidden_keys": [ "hidden_keys": []
"ssh",
"hostname",
"logging",
"logging::[0]",
"logging::evt",
"logging::evt::1024",
"logging::evt::1024::description",
"logging::evt::1024::name",
"logging::evt::1024::priority",
"logging::evt::1024::active",
"logging::evt::1024::destination",
"logging::evt::1026",
"logging::evt::1026::description",
"logging::evt::1026::name",
"logging::evt::1026::priority",
"logging::evt::1026::active",
"logging::evt::1026::destination",
"logging::evt::1027",
"logging::evt::1027::description",
"logging::evt::1027::name",
"logging::evt::1027::priority",
"logging::evt::1027::active",
"logging::evt::1027::destination",
"logging::evt::1028",
"logging::evt::1028::description",
"logging::evt::1028::name",
"logging::evt::1028::priority",
"logging::evt::1028::active",
"logging::evt::1028::destination",
"logging::evt::1029",
"logging::evt::1029::description",
"logging::evt::1029::name",
"logging::evt::1029::priority",
"logging::evt::1029::active",
"logging::evt::1029::destination",
"logging::evt::1030",
"logging::evt::1030::description",
"logging::evt::1030::name",
"logging::evt::1030::priority",
"logging::evt::1030::active",
"logging::evt::1030::destination",
"logging::evt::1031",
"logging::evt::1031::description",
"logging::evt::1031::name",
"logging::evt::1031::priority",
"logging::evt::1031::active",
"logging::evt::1031::destination",
"logging::evt::1032",
"logging::evt::1032::description",
"logging::evt::1032::name",
"logging::evt::1032::priority",
"logging::evt::1032::active",
"logging::evt::1032::destination",
"logging::evt::1033",
"logging::evt::1033::description",
"logging::evt::1033::name",
"logging::evt::1033::priority",
"logging::evt::1033::active",
"logging::evt::1033::destination",
"logging::evt::1034",
"logging::evt::1034::description",
"logging::evt::1034::name",
"logging::evt::1034::priority",
"logging::evt::1034::active",
"logging::evt::1034::destination",
"logging::evt::1035",
"logging::evt::1035::description",
"logging::evt::1035::name",
"logging::evt::1035::priority",
"logging::evt::1035::active",
"logging::evt::1035::destination",
"logging::evt::1043",
"logging::evt::1043::description",
"logging::evt::1043::name",
"logging::evt::1043::priority",
"logging::evt::1043::active",
"logging::evt::1043::destination",
"logging::evt::1044",
"logging::evt::1044::description",
"logging::evt::1044::name",
"logging::evt::1044::priority",
"logging::evt::1044::active",
"logging::evt::1044::destination",
"logging::evt::1045",
"logging::evt::1045::description",
"logging::evt::1045::name",
"logging::evt::1045::priority",
"logging::evt::1045::active",
"logging::evt::1045::destination",
"logging::evt::1046",
"logging::evt::1046::description",
"logging::evt::1046::name",
"logging::evt::1046::priority",
"logging::evt::1046::active",
"logging::evt::1046::destination",
"logging::evt::1047",
"logging::evt::1047::description",
"logging::evt::1047::name",
"logging::evt::1047::priority",
"logging::evt::1047::active",
"logging::evt::1047::destination",
"logging::evt::1060",
"logging::evt::1060::description",
"logging::evt::1060::name",
"logging::evt::1060::priority",
"logging::evt::1060::active",
"logging::evt::1060::destination",
"logging::evt::1061",
"logging::evt::1061::description",
"logging::evt::1061::name",
"logging::evt::1061::priority",
"logging::evt::1061::active",
"logging::evt::1061::destination",
"logging::evt::1062",
"logging::evt::1062::description",
"logging::evt::1062::name",
"logging::evt::1062::priority",
"logging::evt::1062::active",
"logging::evt::1062::destination",
"logging::evt::1063",
"logging::evt::1063::description",
"logging::evt::1063::name",
"logging::evt::1063::priority",
"logging::evt::1063::active",
"logging::evt::1063::destination",
"logging::evt::1064",
"logging::evt::1064::description",
"logging::evt::1064::name",
"logging::evt::1064::priority",
"logging::evt::1064::active",
"logging::evt::1064::destination",
"logging::evt::1065",
"logging::evt::1065::description",
"logging::evt::1065::name",
"logging::evt::1065::priority",
"logging::evt::1065::active",
"logging::evt::1065::destination",
"logging::evt::1066",
"logging::evt::1066::description",
"logging::evt::1066::name",
"logging::evt::1066::priority",
"logging::evt::1066::active",
"logging::evt::1066::destination",
"logging::evt::1067",
"logging::evt::1067::description",
"logging::evt::1067::name",
"logging::evt::1067::priority",
"logging::evt::1067::active",
"logging::evt::1067::destination",
"logging::evt::1068",
"logging::evt::1068::description",
"logging::evt::1068::name",
"logging::evt::1068::priority",
"logging::evt::1068::active",
"logging::evt::1068::destination",
"logging::evt::1069",
"logging::evt::1069::description",
"logging::evt::1069::name",
"logging::evt::1069::priority",
"logging::evt::1069::active",
"logging::evt::1069::destination",
"logging::evt::1070",
"logging::evt::1070::description",
"logging::evt::1070::name",
"logging::evt::1070::priority",
"logging::evt::1070::active",
"logging::evt::1070::destination",
"logging::evt::1071",
"logging::evt::1071::description",
"logging::evt::1071::name",
"logging::evt::1071::priority",
"logging::evt::1071::active",
"logging::evt::1071::destination",
"logging::evt::1072",
"logging::evt::1072::description",
"logging::evt::1072::name",
"logging::evt::1072::priority",
"logging::evt::1072::active",
"logging::evt::1072::destination",
"logging::evt::1073",
"logging::evt::1073::description",
"logging::evt::1073::name",
"logging::evt::1073::priority",
"logging::evt::1073::active",
"logging::evt::1073::destination",
"logging::evt::1075",
"logging::evt::1075::description",
"logging::evt::1075::name",
"logging::evt::1075::priority",
"logging::evt::1075::active",
"logging::evt::1075::destination",
"logging::evt::1076",
"logging::evt::1076::description",
"logging::evt::1076::name",
"logging::evt::1076::priority",
"logging::evt::1076::active",
"logging::evt::1076::destination",
"logging::evt::1077",
"logging::evt::1077::description",
"logging::evt::1077::name",
"logging::evt::1077::priority",
"logging::evt::1077::active",
"logging::evt::1077::destination",
"logging::evt::2002",
"logging::evt::2002::description",
"logging::evt::2002::name",
"logging::evt::2002::priority",
"logging::evt::2002::active",
"logging::evt::2002::destination",
"logging::evt::2003",
"logging::evt::2003::description",
"logging::evt::2003::name",
"logging::evt::2003::priority",
"logging::evt::2003::active",
"logging::evt::2003::destination",
"logging::evt::2005",
"logging::evt::2005::description",
"logging::evt::2005::name",
"logging::evt::2005::priority",
"logging::evt::2005::active",
"logging::evt::2005::destination",
"logging::evt::2006",
"logging::evt::2006::description",
"logging::evt::2006::name",
"logging::evt::2006::priority",
"logging::evt::2006::active",
"logging::evt::2006::destination",
"logging::evt::2020",
"logging::evt::2020::description",
"logging::evt::2020::name",
"logging::evt::2020::priority",
"logging::evt::2020::active",
"logging::evt::2020::destination",
"logging::evt::2024",
"logging::evt::2024::description",
"logging::evt::2024::name",
"logging::evt::2024::priority",
"logging::evt::2024::active",
"logging::evt::2024::destination",
"logging::evt::2025",
"logging::evt::2025::description",
"logging::evt::2025::name",
"logging::evt::2025::priority",
"logging::evt::2025::active",
"logging::evt::2025::destination",
"logging::evt::2026",
"logging::evt::2026::description",
"logging::evt::2026::name",
"logging::evt::2026::priority",
"logging::evt::2026::active",
"logging::evt::2026::destination",
"logging::evt::2028",
"logging::evt::2028::description",
"logging::evt::2028::name",
"logging::evt::2028::priority",
"logging::evt::2028::active",
"logging::evt::2028::destination",
"logging::evt::2030",
"logging::evt::2030::description",
"logging::evt::2030::name",
"logging::evt::2030::priority",
"logging::evt::2030::active",
"logging::evt::2030::destination",
"logging::evt::2034",
"logging::evt::2034::description",
"logging::evt::2034::name",
"logging::evt::2034::priority",
"logging::evt::2034::active",
"logging::evt::2034::destination",
"logging::evt::2046",
"logging::evt::2046::description",
"logging::evt::2046::name",
"logging::evt::2046::priority",
"logging::evt::2046::active",
"logging::evt::2046::destination",
"logging::evt::2050",
"logging::evt::2050::description",
"logging::evt::2050::name",
"logging::evt::2050::priority",
"logging::evt::2050::active",
"logging::evt::2050::destination",
"logging::evt::2051",
"logging::evt::2051::description",
"logging::evt::2051::name",
"logging::evt::2051::priority",
"logging::evt::2051::active",
"logging::evt::2051::destination",
"logging::evt::2052",
"logging::evt::2052::description",
"logging::evt::2052::name",
"logging::evt::2052::priority",
"logging::evt::2052::active",
"logging::evt::2052::destination",
"logging::evt::2053",
"logging::evt::2053::description",
"logging::evt::2053::name",
"logging::evt::2053::priority",
"logging::evt::2053::active",
"logging::evt::2053::destination",
"logging::evt::2054",
"logging::evt::2054::description",
"logging::evt::2054::name",
"logging::evt::2054::priority",
"logging::evt::2054::active",
"logging::evt::2054::destination",
"logging::evt::2055",
"logging::evt::2055::description",
"logging::evt::2055::name",
"logging::evt::2055::priority",
"logging::evt::2055::active",
"logging::evt::2055::destination",
"logging::evt::2056",
"logging::evt::2056::description",
"logging::evt::2056::name",
"logging::evt::2056::priority",
"logging::evt::2056::active",
"logging::evt::2056::destination",
"logging::evt::2057",
"logging::evt::2057::description",
"logging::evt::2057::name",
"logging::evt::2057::priority",
"logging::evt::2057::active",
"logging::evt::2057::destination",
"logging::evt::2061",
"logging::evt::2061::description",
"logging::evt::2061::name",
"logging::evt::2061::priority",
"logging::evt::2061::active",
"logging::evt::2061::destination",
"logging::evt::2062",
"logging::evt::2062::description",
"logging::evt::2062::name",
"logging::evt::2062::priority",
"logging::evt::2062::active",
"logging::evt::2062::destination",
"logging::evt::2064",
"logging::evt::2064::description",
"logging::evt::2064::name",
"logging::evt::2064::priority",
"logging::evt::2064::active",
"logging::evt::2064::destination",
"logging::evt::2065",
"logging::evt::2065::description",
"logging::evt::2065::name",
"logging::evt::2065::priority",
"logging::evt::2065::active",
"logging::evt::2065::destination",
"logging::[1]",
"logging::[2]",
"no",
"ipdr",
"ipdr::[0]",
"ipdr::[1]",
"ipdr::[2]",
"ipdr::[3]",
"ipdr::[4]",
"snmp-server",
"snmp-server::[0]",
"snmp-server::[1]",
"snmp-server::[2]",
"snmp-server::[3]",
"snmp-server::[4]",
"snmp-server::[5]",
"snmp-server::[6]",
"snmp-server::[7]",
"snmp-server::[8]",
"snmp-server::[9]",
"aaa",
"aaa::[0]",
"aaa::[1]",
"aaa::[2]",
"aaa::[3]",
"aaa::[4]",
"aaa::[5]",
"radius-server",
"radius-server::[0]",
"radius-server::[1]",
"privilege",
"privilege::0",
"privilege::0::password",
"privilege::1",
"privilege::1::password",
"privilege::15",
"privilege::15::password",
"cli",
"cli::[0]",
"cli::[1]",
"packetcable",
"packetcable::[0]",
"packetcable::[1]",
"packetcable::[2]",
"packetcable::[3]",
"packetcable::[4]",
"packetcable::[5]",
"system",
"system::[0]",
"system::[1]",
"system::[2]",
"system::[3]",
"system::[4]",
"system::[5]",
"system::[6]",
"system::[7]",
"system::[8]",
"system::[9]",
"system::[10]",
"system::[11]",
"system::[12]",
"system::[13]",
"system::[14]",
"system::[15]",
"system::[16]",
"system::[17]",
"system::[18]",
"system::[19]",
"system::[20]",
"system::[21]",
"system::[22]",
"system::[23]",
"system::[24]",
"system::[25]",
"system::[26]",
"system::proto-throttle",
"system::proto-throttle::total",
"system::proto-throttle::total::rate-pps",
"system::proto-throttle::total::max-burst-pkts",
"system::proto-throttle::other",
"system::proto-throttle::other::rate-pps",
"system::proto-throttle::other::max-burst-pkts",
"system::proto-throttle::arp",
"system::proto-throttle::arp::rate-pps",
"system::proto-throttle::arp::max-burst-pkts",
"system::proto-throttle::dhcp",
"system::proto-throttle::dhcp::rate-pps",
"system::proto-throttle::dhcp::max-burst-pkts",
"system::proto-throttle::dhcpv6",
"system::proto-throttle::dhcpv6::rate-pps",
"system::proto-throttle::dhcpv6::max-burst-pkts",
"system::proto-throttle::nd",
"system::proto-throttle::nd::rate-pps",
"system::proto-throttle::nd::max-burst-pkts",
"system::proto-throttle::rip",
"system::proto-throttle::rip::rate-pps",
"system::proto-throttle::rip::max-burst-pkts",
"system::[27]",
"system::[28]",
"network",
"network::[0]",
"network::[1]"
]
} }