diff --git a/cmts_leaf_options_cache.json b/cmts_leaf_options_cache.json index 3636ad9..489057f 100644 --- a/cmts_leaf_options_cache.json +++ b/cmts_leaf_options_cache.json @@ -98704,5 +98704,329 @@ "type": "string_or_number", "current_value": null, "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[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[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", + "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[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[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[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[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[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[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[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[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[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[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[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[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[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[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[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[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[clear cable modem] ", + "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[debug cable modem] ", + "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[debug cable rpd] ", + "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[debug cluster server] ", + "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[show cable rpd] ", + "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[show cluster server] ", + "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 ", + "options": [], + "type": "string_or_number", + "current_value": null, + "updated_at": 1778489033 } } \ No newline at end of file diff --git a/shared.py b/shared.py index 44e0bd4..62e28f2 100644 --- a/shared.py +++ b/shared.py @@ -2,6 +2,8 @@ import asyncio import json import os +import re +from collections import defaultdict DEBUG_MODE = False @@ -22,66 +24,156 @@ CMTS_DEVICE = { def parse_cli_to_tree(cli_text: str) -> dict: """ - 將 CMTS 的 CLI 配置文字,根據縮排轉換為巢狀 JSON (Dictionary) + 將 CMTS 的 CLI 配置文字轉換為完美的邏輯巢狀 JSON (兩階段解析法) """ - # 過濾掉空行與註解 (!) - lines = [line for line in cli_text.splitlines() if line.strip() and line.strip() != '!'] - - def parse_block(start_idx: int, base_indent: int): - block = {} + lines = [line.rstrip() for line in cli_text.splitlines() if line.strip()] + + # ========================================== + # 階段 1:建立實體樹 (依據縮排與 '!') + # ========================================== + def build_raw_tree(start_idx, current_indent): + nodes = [] i = start_idx while i < len(lines): 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()) - if indent < base_indent: - break - - content = line.strip() + # 縮排退回,隱含結束當前區塊 (不消耗該行) + if indent < current_indent: + return nodes, i + # 探測是否有子節點 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 + next_line = lines[i+1] + next_stripped = next_line.strip() + if next_stripped != '!': + next_indent = len(next_line) - len(next_line.lstrip()) + if next_indent > 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 - + children, next_i = build_raw_tree(i + 1, next_indent) + nodes.append({"type": "folder", "content": stripped, "children": children}) 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 - + nodes.append({"type": "leaf", "content": stripped}) i += 1 - - return block, i + return nodes, i - tree, _ = parse_block(0, 0) - return tree + raw_tree, _ = build_raw_tree(0, 0) + + # ========================================== + # 階段 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): """將平坦的 CLI 字典,依照空白字元拆分成深層巢狀結構 (具備終極型態衝突保護)""" diff --git a/static/app.js b/static/app.js index 0492fde..6928650 100644 --- a/static/app.js +++ b/static/app.js @@ -89,6 +89,28 @@ function resetIdleTimer() { window.addEventListener('mousemove', resetIdleTimer); window.addEventListener('keydown', 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) { if (!text) return ''; @@ -1514,77 +1536,114 @@ async function executeBondingConfig() { } // ========================================== -// 💡 6. 完整設備配置樹狀圖邏輯 (支援資料夾層級鎖定) +// 💡 6. 完整設備配置樹狀圖邏輯 (支援資料夾層級鎖定與虛擬群組視覺優化) // ========================================== -function buildTree(data, parentPath = "") { - if (typeof data !== 'object' || data === null) { - return `${data}`; - } +function buildTree(node, path = '') { + let html = ''; + if (!node || typeof node !== 'object') return html; - let html = '
'; - for (const key in data) { - const value = data[key]; - const currentPath = parentPath ? `${parentPath}::${key}` : key; + for (const [key, value] of Object.entries(node)) { + const currentPath = path ? `${path}::${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 hasNestedObject = Object.values(value).some(v => typeof v === 'object' && v !== null); + const folderIcon = hasNestedObject ? '📁' : '📑'; + const folderSuffix = hasNestedObject ? '' : '(指令群組)'; + + // 🌟 定義專業的 SVG 向量圖示 + const expandAllIcon = ``; + const collapseAllIcon = ``; + + // 🌟 全部展開/收合按鈕 (使用 SVG 並加入懸停變色效果) + const expandCollapseBtns = hasNestedObject ? ` + + + ${expandAllIcon} + + + ${collapseAllIcon} + + + ` : ''; + + // ✅ 加入 title="${cliCommand}" 提供懸停提示 html += ` -
- - - +
+ + ${folderIcon} + ${key}${folderSuffix} + ${expandCollapseBtns} - - - ${key} - -
- + onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.3" title="鎖定並編輯此群組"> ✏️ - -
- -
- ${buildTree(value, currentPath)} +
+ ${buildTree(value, currentPath)}
`; - } else { - // 📄 渲染單一設定項目 (加入獨立編輯按鈕) + } else { + // 🌟 處理單一設定項目 (Leaf) + const isVirtualIndex = /^\[\d+\]$/.test(key); const safeValue = (value === null ? '' : value).toString().replace(/"/g, """); const elementId = `leaf-${currentPath.replace(/[^a-zA-Z0-9]/g, '-')}`; - html += ` - -
- 📄 ${key}: - + let displayContent = ''; + + if (isVirtualIndex) { + displayContent = ` + 📄 - ${value === null ? '' : value} + ${safeValue} + `; + } else { + displayContent = ` + 📄 ${key}: + + ${safeValue} + + `; + } + + // ✅ 加入 title="${cliCommand}" 提供懸停提示 + html += ` +
+ ${displayContent}
- ✏️ - -
'; return html; } @@ -1696,31 +1754,45 @@ function buildRealFilterTree(data, parentPath, hiddenKeys) { const isChecked = hiddenKeys.includes(currentPath) ? "checked" : ""; 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 = ``; + + if (isVirtualGroup) { + iconHtml = `📑`; + displayName = `${key} (指令群組)`; + } else if (isVirtualIndex) { + iconHtml = `📦`; + displayName = `項目 ${key}`; + } + html += `
- - + - + onclick="event.stopPropagation();" onchange="toggleChildCheckboxes(this)" style="margin-right: 8px;"> - - ${key} + ${iconHtml} + ${displayName} - -
${buildRealFilterTree(value, currentPath, hiddenKeys)}
`; } else { + const isVirtualIndex = /^\[\d+\]$/.test(key); + let displayName = isVirtualIndex ? `項目 ${key}` : `${key}`; const safeValue = (value === null ? '' : value).toString().replace(/"/g, """); + html += `
- 📄 ${key}: + onchange="updateParentCheckboxState(this)" style="margin-right: 8px;"> + 📄 ${displayName}: ${safeValue}
`; diff --git a/system_settings.json b/system_settings.json index 848d560..ba3788a 100644 --- a/system_settings.json +++ b/system_settings.json @@ -1,453 +1,3 @@ { - "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]" - ] + "hidden_keys": [] } \ No newline at end of file