fix: 修復 Single Page Application (SPA) 「狀態殘留 (State Leakage)」問題
fix: 修復「延遲渲染 (Lazy Rendering)」與「編輯模式」之間的一個極度邊角的情境 (Edge Case) 衝突。
This commit is contained in:
parent
8f026aac9e
commit
387d1d600a
|
|
@ -9,3 +9,5 @@ filters_*.json
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
target_code.txt
|
||||||
|
all_code.txt
|
||||||
|
|
|
||||||
167
all_code.txt
167
all_code.txt
|
|
@ -690,9 +690,9 @@ FILE: index.html
|
||||||
<div class="global-settings">
|
<div class="global-settings">
|
||||||
<label><strong>🌐 目標設備:</strong></label>
|
<label><strong>🌐 目標設備:</strong></label>
|
||||||
<!-- 🌟 修改後 (徹底淨化) -->
|
<!-- 🌟 修改後 (徹底淨化) -->
|
||||||
<input type="text" id="cmtsHost" placeholder="IP 地址" style="width: 130px;">
|
<input type="text" id="cmtsHost" aria-label="IP 地址" placeholder="IP 地址" style="width: 130px;">
|
||||||
<input type="text" id="cmtsUser" placeholder="帳號" style="width: 100px;">
|
<input type="text" id="cmtsUser" aria-label="帳號" placeholder="帳號" style="width: 100px;">
|
||||||
<input type="password" id="cmtsPass" placeholder="密碼" style="width: 100px;">
|
<input type="password" id="cmtsPass" aria-label="密碼" placeholder="密碼" style="width: 100px;">
|
||||||
|
|
||||||
<div style="margin-left: 15px; display: flex; align-items: center; gap: 10px;">
|
<div style="margin-left: 15px; display: flex; align-items: center; gap: 10px;">
|
||||||
<button onclick="connectWebSocket()" id="btnConnect" class="btn-modern btn-connect">連線至 CMTS</button>
|
<button onclick="connectWebSocket()" id="btnConnect" class="btn-modern btn-connect">連線至 CMTS</button>
|
||||||
|
|
@ -715,7 +715,7 @@ FILE: index.html
|
||||||
<div id="cli-tab" class="tab-content active">
|
<div id="cli-tab" class="tab-content active">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label><strong>快捷指令:</strong></label>
|
<label><strong>快捷指令:</strong></label>
|
||||||
<select id="fixedCmd">
|
<select id="fixedCmd" aria-label="選擇快捷指令" >
|
||||||
<option value="show cable modem | nomore">數據機狀態 (show cable modem | nomore)</option>
|
<option value="show cable modem | nomore">數據機狀態 (show cable modem | nomore)</option>
|
||||||
<option value="show cable rpd | nomore">RPD 狀態 (show cable rpd | nomore)</option>
|
<option value="show cable rpd | nomore">RPD 狀態 (show cable rpd | nomore)</option>
|
||||||
<option value="show running-config | nomore">系統實時設定檔 (show running-config | nomore)</option>
|
<option value="show running-config | nomore">系統實時設定檔 (show running-config | nomore)</option>
|
||||||
|
|
@ -736,7 +736,7 @@ FILE: index.html
|
||||||
|
|
||||||
<div class="control-group" style="background: #ffffff; padding: 15px 25px; border-radius: 8px; margin-bottom: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); border: 1px solid #e2e8f0;">
|
<div class="control-group" style="background: #ffffff; padding: 15px 25px; border-radius: 8px; margin-bottom: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); border: 1px solid #e2e8f0;">
|
||||||
<label style="font-weight: bold; color: #2c3e50; font-size: 16px; margin-right: 10px;">📌 選擇查詢任務:</label>
|
<label style="font-weight: bold; color: #2c3e50; font-size: 16px; margin-right: 10px;">📌 選擇查詢任務:</label>
|
||||||
<select id="queryTask" onchange="switchQueryTask()" style="width: 400px; max-width: 100%; font-weight: bold; font-size: 15px; padding: 8px; background-color: #f8f9fa;">
|
<select id="queryTask" onchange="switchQueryTask()" aria-label="選擇查詢任務" style="width: 400px; max-width: 100%; font-weight: bold; font-size: 15px; padding: 8px; background-color: #f8f9fa;">
|
||||||
<option value="form-cm-query">🔎 Cable 狀態綜合查詢</option>
|
<option value="form-cm-query">🔎 Cable 狀態綜合查詢</option>
|
||||||
<option value="form-rpd-query">📡 RPD 狀態綜合查詢</option>
|
<option value="form-rpd-query">📡 RPD 狀態綜合查詢</option>
|
||||||
<option value="form-cm-diagnostics">🩺 CM 一鍵診斷中心</option>
|
<option value="form-cm-diagnostics">🩺 CM 一鍵診斷中心</option>
|
||||||
|
|
@ -748,13 +748,13 @@ FILE: index.html
|
||||||
<h3 style="margin-top: 0; font-size: 18px; color: #2980b9; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">Cable 狀態綜合查詢 (show cable modem...)</h3>
|
<h3 style="margin-top: 0; font-size: 18px; color: #2980b9; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">Cable 狀態綜合查詢 (show cable modem...)</h3>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>目標設備 (Cable Modem MAC)</label>
|
<label for="queryTargetCm">目標設備 (Cable Modem MAC)</label>
|
||||||
<input type="text" id="queryTargetCm" list="cm-mac-list" placeholder="例: 6467.7240.4076 (留白則查詢全體)">
|
<input type="text" id="queryTargetCm" list="cm-mac-list" placeholder="例: 6467.7240.4076 (留白則查詢全體)">
|
||||||
<datalist id="cm-mac-list"></datalist>
|
<datalist id="cm-mac-list"></datalist>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>查詢動作 (Action)</label>
|
<label>查詢動作 (Action)</label>
|
||||||
<select id="queryTypeCm" onchange="toggleQueryInputs('Cm')">
|
<select id="queryTypeCm" onchange="toggleQueryInputs('Cm')" aria-label="選擇 Cable Modem 查詢動作">
|
||||||
<optgroup label="Cable Modem 查詢 (支援特定目標或全體)">
|
<optgroup label="Cable Modem 查詢 (支援特定目標或全體)">
|
||||||
<option value="base">基本狀態 (show cable modem)</option>
|
<option value="base">基本狀態 (show cable modem)</option>
|
||||||
<option value="cpe">CPE 資訊 (cpe)</option>
|
<option value="cpe">CPE 資訊 (cpe)</option>
|
||||||
|
|
@ -798,13 +798,13 @@ FILE: index.html
|
||||||
<h3 style="margin-top: 0; font-size: 18px; color: #e67e22; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">RPD 狀態綜合查詢 (show cable rpd...)</h3>
|
<h3 style="margin-top: 0; font-size: 18px; color: #e67e22; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">RPD 狀態綜合查詢 (show cable rpd...)</h3>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>目標設備 (RPD VC:VS)</label>
|
<label for="queryTargetRpd">目標設備 (RPD VC:VS)</label>
|
||||||
<input type="text" id="queryTargetRpd" list="rpd-vcvs-list" placeholder="例: 13:0 (留白則查詢全體)">
|
<input type="text" id="queryTargetRpd" list="rpd-vcvs-list" placeholder="例: 13:0 (留白則查詢全體)">
|
||||||
<datalist id="rpd-vcvs-list"></datalist>
|
<datalist id="rpd-vcvs-list"></datalist>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>查詢動作 (Action)</label>
|
<label>查詢動作 (Action)</label>
|
||||||
<select id="queryTypeRpd" onchange="toggleQueryInputs('Rpd')">
|
<select id="queryTypeRpd" onchange="toggleQueryInputs('Rpd')" aria-label="選擇 RPD 查詢動作">
|
||||||
<option value="rpd_base">基本狀態 (show cable rpd)</option>
|
<option value="rpd_base">基本狀態 (show cable rpd)</option>
|
||||||
<option value="rpd_verbose">詳細資訊 (verbose)</option>
|
<option value="rpd_verbose">詳細資訊 (verbose)</option>
|
||||||
<option value="rpd_ptp_time">PTP Time Property (ptp time-property)</option>
|
<option value="rpd_ptp_time">PTP Time Property (ptp time-property)</option>
|
||||||
|
|
@ -835,7 +835,7 @@ FILE: index.html
|
||||||
|
|
||||||
<!-- 搜尋區塊 -->
|
<!-- 搜尋區塊 -->
|
||||||
<div class="control-group" style="background: #fdfefe; padding: 15px; border-radius: 6px; border: 1px solid #e8daef; margin-bottom: 20px;">
|
<div class="control-group" style="background: #fdfefe; padding: 15px; border-radius: 6px; border: 1px solid #e8daef; margin-bottom: 20px;">
|
||||||
<label style="font-weight: bold; color: #2c3e50; font-size: 15px;">🎯 目標 CM MAC:</label>
|
<label for="diagMacInput" style="font-weight: bold; color: #2c3e50; font-size: 15px;">🎯 目標 CM MAC:</label>
|
||||||
<input type="text" id="diagMacInput" list="diag-cm-mac-list" placeholder="例如: 6467.7240.4076" style="width: 200px; padding: 6px 8px; font-size: 14px; margin: 0 10px; border: 1px solid #bdc3c7; border-radius: 4px;">
|
<input type="text" id="diagMacInput" list="diag-cm-mac-list" placeholder="例如: 6467.7240.4076" style="width: 200px; padding: 6px 8px; font-size: 14px; margin: 0 10px; border: 1px solid #bdc3c7; border-radius: 4px;">
|
||||||
<datalist id="diag-cm-mac-list"></datalist>
|
<datalist id="diag-cm-mac-list"></datalist>
|
||||||
<button onclick="runCmDiagnostics()" id="btnRunDiag" class="btn-modern btn-scan" style="background-color: #8e44ad;">🚀 執行深度診斷</button>
|
<button onclick="runCmDiagnostics()" id="btnRunDiag" class="btn-modern btn-scan" style="background-color: #8e44ad;">🚀 執行深度診斷</button>
|
||||||
|
|
@ -906,7 +906,7 @@ FILE: index.html
|
||||||
|
|
||||||
<div class="control-group" style="background: #ffffff; padding: 10px 15px; border-radius: 8px; margin-bottom: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); border: 1px solid #e2e8f0;">
|
<div class="control-group" style="background: #ffffff; padding: 10px 15px; border-radius: 8px; margin-bottom: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); border: 1px solid #e2e8f0;">
|
||||||
<label style="font-weight: bold; color: #c0392b; font-size: 16px; margin-right: 10px;">📌 選擇配置任務:</label>
|
<label style="font-weight: bold; color: #c0392b; font-size: 16px; margin-right: 10px;">📌 選擇配置任務:</label>
|
||||||
<select id="configTask" onchange="switchConfigTask()" style="width: 400px; max-width: 100%; font-weight: bold; font-size: 15px; padding: 8px; background-color: #fcf3f2; border-color: #fadbd8;">
|
<select id="configTask" aria-label="選擇配置任務" onchange="switchConfigTask()" style="width: 400px; max-width: 100%; font-weight: bold; font-size: 15px; padding: 8px; background-color: #fcf3f2; border-color: #fadbd8;">
|
||||||
<!-- 🌟 拆分為兩個獨立的選項 -->
|
<!-- 🌟 拆分為兩個獨立的選項 -->
|
||||||
<option value="form-running-config">🌳 設備配置樹狀圖 (running-config)</option>
|
<option value="form-running-config">🌳 設備配置樹狀圖 (running-config)</option>
|
||||||
<option value="form-full-config">🌳 完整設備配置樹狀圖 (full configuration)</option>
|
<option value="form-full-config">🌳 完整設備配置樹狀圖 (full configuration)</option>
|
||||||
|
|
@ -920,7 +920,7 @@ FILE: index.html
|
||||||
<h3 style="margin-top: 0; font-size: 18px; color: #c0392b; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">⚠️ MAC Domain 狀態感知配置精靈</h3>
|
<h3 style="margin-top: 0; font-size: 18px; color: #c0392b; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">⚠️ MAC Domain 狀態感知配置精靈</h3>
|
||||||
|
|
||||||
<div class="control-group" style="background: #fdf2e9; padding: 15px; border-radius: 6px; border: 1px solid #fadbd8;">
|
<div class="control-group" style="background: #fdf2e9; padding: 15px; border-radius: 6px; border: 1px solid #fadbd8;">
|
||||||
<label style="font-weight: bold; color: #d35400;">1. 目標 MAC Domain:</label>
|
<label for="cfgMacDomain" style="font-weight: bold; color: #d35400;">1. 目標 MAC Domain:</label>
|
||||||
<input type="text" id="cfgMacDomain" list="mac-domain-list" placeholder="請先點擊上方載入任務..." style="width: 220px; padding: 6px; font-weight: bold; border: 1px solid #fadbd8; border-radius: 4px; background-color: #fff;">
|
<input type="text" id="cfgMacDomain" list="mac-domain-list" placeholder="請先點擊上方載入任務..." style="width: 220px; padding: 6px; font-weight: bold; border: 1px solid #fadbd8; border-radius: 4px; background-color: #fff;">
|
||||||
<datalist id="mac-domain-list"></datalist>
|
<datalist id="mac-domain-list"></datalist>
|
||||||
<button onclick="fetchMacDomainConfig()" class="btn-modern btn-load">🔍 讀取現有配置</button>
|
<button onclick="fetchMacDomainConfig()" class="btn-modern btn-load">🔍 讀取現有配置</button>
|
||||||
|
|
@ -931,39 +931,39 @@ FILE: index.html
|
||||||
<!-- Common Settings -->
|
<!-- Common Settings -->
|
||||||
<h4 style="color: #2980b9; border-left: 4px solid #2980b9; padding-left: 8px;">Common Settings</h4>
|
<h4 style="color: #2980b9; border-left: 4px solid #2980b9; padding-left: 8px;">Common Settings</h4>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row"><label>IP Provisioning Mode</label><select id="g_ip_prov"><option value="alternate">alternate</option><option value="dual-stack">dual-stack</option><option value="ipv4-only">ipv4-only</option><option value="ipv6-only">ipv6-only</option></select></div>
|
<div class="form-row"><label for="g_ip_prov">IP Provisioning Mode</label><select id="g_ip_prov"><option value="alternate">alternate</option><option value="dual-stack">dual-stack</option><option value="ipv4-only">ipv4-only</option><option value="ipv6-only">ipv6-only</option></select></div>
|
||||||
<div class="form-row"><label>Diplexer Band Edge Control</label><select id="g_diplexer"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_diplexer">Diplexer Band Edge Control</label><select id="g_diplexer"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>CM Battery Mode 3.1</label><select id="g_bat31"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_bat31">CM Battery Mode 3.1</label><select id="g_bat31"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>CM Battery Mode 3.0</label><select id="g_bat30"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_bat30">CM Battery Mode 3.0</label><select id="g_bat30"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>DOCSIS 4.0</label><select id="g_docsis40"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_docsis40">DOCSIS 4.0</label><select id="g_docsis40"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>DS Dynamic Bonding Group</label><select id="g_ds_dyn" onchange="toggleGroupVisibility()"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_ds_dyn">DS Dynamic Bonding Group</label><select id="g_ds_dyn" onchange="toggleGroupVisibility()"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>US Dynamic Bonding Group</label><select id="g_us_dyn" onchange="toggleGroupVisibility()"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_us_dyn">US Dynamic Bonding Group</label><select id="g_us_dyn" onchange="toggleGroupVisibility()"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- [Basic] DS/US Channel Sets -->
|
<!-- [Basic] DS/US Channel Sets -->
|
||||||
<h4 style="color: #27ae60; border-left: 4px solid #27ae60; padding-left: 8px; margin-top: 30px;">[Basic] DS/US Channel Sets</h4>
|
<h4 style="color: #27ae60; border-left: 4px solid #27ae60; padding-left: 8px; margin-top: 30px;">[Basic] DS/US Channel Sets</h4>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row"><label>Admin State</label><select id="b_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
<div class="form-row"><label for="b_admin">Admin State</label><select id="b_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
||||||
<div class="form-row"><label>DS Primary Set (0..157)</label><input type="text" id="b_ds_pri" placeholder="例: 0-2"></div>
|
<div class="form-row"><label for="b_ds_pri">DS Primary Set (0..157)</label><input type="text" id="b_ds_pri" placeholder="例: 0-2"></div>
|
||||||
<div class="form-row"><label>DS Non-Primary Set (0..157)</label><input type="text" id="b_ds_non_pri" placeholder="例: 3-4"></div>
|
<div class="form-row"><label for="b_ds_non_pri">DS Non-Primary Set (0..157)</label><input type="text" id="b_ds_non_pri" placeholder="例: 3-4"></div>
|
||||||
<div class="form-row"><label>US PHY Channel Set (0..255)</label><input type="text" id="b_us_phy" placeholder="例: 0-3"></div>
|
<div class="form-row"><label for="b_us_phy">US PHY Channel Set (0..255)</label><input type="text" id="b_us_phy" placeholder="例: 0-3"></div>
|
||||||
<div class="form-row"><label>DS OFDM Set (0..7)</label><input type="text" id="b_ds_ofdm" placeholder="例: 0"></div>
|
<div class="form-row"><label for="b_ds_ofdm">DS OFDM Set (0..7)</label><input type="text" id="b_ds_ofdm" placeholder="例: 0"></div>
|
||||||
<div class="form-row"><label>US OFDMA Set (0..1)</label><input type="text" id="b_us_ofdma" placeholder="例: 0"></div>
|
<div class="form-row"><label for="b_us_ofdma">US OFDMA Set (0..1)</label><input type="text" id="b_us_ofdma" placeholder="例: 0"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- [Static] Downstream Bonding Groups -->
|
<!-- [Static] Downstream Bonding Groups -->
|
||||||
<div id="section_group_ds" style="margin-top: 30px;">
|
<div id="section_group_ds" style="margin-top: 30px;">
|
||||||
<h4 style="color: #8e44ad; border-left: 4px solid #8e44ad; padding-left: 8px;">[Static] Downstream Bonding Groups</h4>
|
<h4 style="color: #8e44ad; border-left: 4px solid #8e44ad; padding-left: 8px;">[Static] Downstream Bonding Groups</h4>
|
||||||
<div class="control-group" style="background: #f4f6f7; padding: 10px; border-radius: 4px;">
|
<div class="control-group" style="background: #f4f6f7; padding: 10px; border-radius: 4px;">
|
||||||
<label>選擇要編輯的 DS Group:</label>
|
<label for="select_ds_group">選擇要編輯的 DS Group:</label>
|
||||||
<select id="select_ds_group" onchange="loadDsGroupData()" style="width: 200px;"></select>
|
<select id="select_ds_group" onchange="loadDsGroupData()" style="width: 200px;"></select>
|
||||||
<input type="text" id="input_new_ds_group" placeholder="輸入新 Group 名稱 (例: D4A)" style="display: none; width: 200px;">
|
<input type="text" id="input_new_ds_group" aria-label="輸入新 DS Group 名稱" placeholder="輸入新 Group 名稱 (例: D4A)" style="display: none; width: 200px;">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row"><label>Admin State</label><select id="ds_g_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
<div class="form-row"><label for="ds_g_admin">Admin State</label><select id="ds_g_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
||||||
<div class="form-row"><label>Down Channel Set (0..157)</label><input type="text" id="ds_g_down" placeholder="例: 0-4"></div>
|
<div class="form-row"><label for="ds_g_down">Down Channel Set (0..157)</label><input type="text" id="ds_g_down" placeholder="例: 0-4"></div>
|
||||||
<div class="form-row"><label>OFDM Channel Set (0..7)</label><input type="text" id="ds_g_ofdm" placeholder="例: 0-1"></div>
|
<div class="form-row"><label for="ds_g_ofdm">OFDM Channel Set (0..7)</label><input type="text" id="ds_g_ofdm" placeholder="例: 0-1"></div>
|
||||||
<div class="form-row"><label>FDX OFDM Channel Set (0..7)</label><input type="text" id="ds_g_fdx" placeholder="例: 0-2"></div>
|
<div class="form-row"><label for="ds_g_fdx">FDX OFDM Channel Set (0..7)</label><input type="text" id="ds_g_fdx" placeholder="例: 0-2"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -971,15 +971,15 @@ FILE: index.html
|
||||||
<div id="section_group_us" style="margin-top: 30px;">
|
<div id="section_group_us" style="margin-top: 30px;">
|
||||||
<h4 style="color: #f39c12; border-left: 4px solid #f39c12; padding-left: 8px;">[Static] Upstream Bonding Groups</h4>
|
<h4 style="color: #f39c12; border-left: 4px solid #f39c12; padding-left: 8px;">[Static] Upstream Bonding Groups</h4>
|
||||||
<div class="control-group" style="background: #f4f6f7; padding: 10px; border-radius: 4px;">
|
<div class="control-group" style="background: #f4f6f7; padding: 10px; border-radius: 4px;">
|
||||||
<label>選擇要編輯的 US Group:</label>
|
<label for="select_us_group">選擇要編輯的 US Group:</label>
|
||||||
<select id="select_us_group" onchange="loadUsGroupData()" style="width: 200px;"></select>
|
<select id="select_us_group" onchange="loadUsGroupData()" style="width: 200px;"></select>
|
||||||
<input type="text" id="input_new_us_group" placeholder="輸入新 Group 名稱 (例: U4A)" style="display: none; width: 200px;">
|
<input type="text" id="input_new_us_group" aria-label="輸入新 US Group 名稱" placeholder="輸入新 Group 名稱 (例: U4A)" style="display: none; width: 200px;">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row"><label>Admin State</label><select id="us_g_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
<div class="form-row"><label for="us_g_admin">Admin State</label><select id="us_g_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
||||||
<div class="form-row"><label>US Channel Set</label><input type="text" id="us_g_us" placeholder="例: 0-3.0"></div>
|
<div class="form-row"><label for="us_g_us">US Channel Set</label><input type="text" id="us_g_us" placeholder="例: 0-3.0"></div>
|
||||||
<div class="form-row"><label>OFDMA Channel Set (0..1)</label><input type="text" id="us_g_ofdma" placeholder="例: 0"></div>
|
<div class="form-row"><label for="us_g_ofdma">OFDMA Channel Set (0..1)</label><input type="text" id="us_g_ofdma" placeholder="例: 0"></div>
|
||||||
<div class="form-row"><label>FDX OFDMA Channel Set (0..5)</label><input type="text" id="us_g_fdx" placeholder="例: 0-5"></div>
|
<div class="form-row"><label for="us_g_fdx">FDX OFDMA Channel Set (0..5)</label><input type="text" id="us_g_fdx" placeholder="例: 0-5"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1076,7 +1076,7 @@ FILE: index.html
|
||||||
|
|
||||||
<!-- 🌟 新增:過濾器模式切換下拉選單 -->
|
<!-- 🌟 新增:過濾器模式切換下拉選單 -->
|
||||||
<div style="margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
|
<div style="margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
|
||||||
<label style="font-weight: bold; color: #2c3e50;">🎯 選擇要編輯的過濾器:</label>
|
<label for="filter-mode-select" style="font-weight: bold; color: #2c3e50;">🎯 選擇要編輯的過濾器:</label>
|
||||||
<select id="filter-mode-select" onchange="switchFilterMode()" style="padding: 6px 10px; border-radius: 4px; border: 1px solid #bdc3c7; font-weight: bold; font-size: 14px; background-color: #fcf3f2; color: #c0392b;">
|
<select id="filter-mode-select" onchange="switchFilterMode()" style="padding: 6px 10px; border-radius: 4px; border: 1px solid #bdc3c7; font-weight: bold; font-size: 14px; background-color: #fcf3f2; color: #c0392b;">
|
||||||
<option value="running">Running 配置過濾器</option>
|
<option value="running">Running 配置過濾器</option>
|
||||||
<option value="full">Full 配置過濾器</option>
|
<option value="full">Full 配置過濾器</option>
|
||||||
|
|
@ -1146,13 +1146,13 @@ FILE: index.html
|
||||||
<!-- 表單網格區 -->
|
<!-- 表單網格區 -->
|
||||||
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 15px;">
|
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 15px;">
|
||||||
<div>
|
<div>
|
||||||
<label style="display: block; font-size: 14px; font-weight: bold; color: #34495e; margin-bottom: 8px;">
|
<label for="snapshotName" style="display: block; font-size: 14px; font-weight: bold; color: #34495e; margin-bottom: 8px;">
|
||||||
快照名稱 <span style="color: #e74c3c;">*</span>
|
快照名稱 <span style="color: #e74c3c;">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="snapshotName" placeholder="例如: 例行備份、升級前備份" style="width: 100%; padding: 10px 12px; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 14px; transition: border-color 0.2s;">
|
<input type="text" id="snapshotName" placeholder="例如: 例行備份、升級前備份" style="width: 100%; padding: 10px 12px; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 14px; transition: border-color 0.2s;">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style="display: block; font-size: 14px; font-weight: bold; color: #34495e; margin-bottom: 8px;">
|
<label for="backupConfigType" style="display: block; font-size: 14px; font-weight: bold; color: #34495e; margin-bottom: 8px;">
|
||||||
配置類型
|
配置類型
|
||||||
</label>
|
</label>
|
||||||
<select id="backupConfigType" style="width: 100%; padding: 10px 12px; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 14px; background-color: #f8f9fa; cursor: pointer;">
|
<select id="backupConfigType" style="width: 100%; padding: 10px 12px; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 14px; background-color: #f8f9fa; cursor: pointer;">
|
||||||
|
|
@ -1182,9 +1182,9 @@ FILE: index.html
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div style="display: flex; gap: 10px; align-items: center;">
|
<div style="display: flex; gap: 10px; align-items: center;">
|
||||||
<input type="text" id="filter-keyword" placeholder="🔍 搜尋名稱或描述..." class="edit-input" style="width: 180px; padding: 6px 10px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px;" onkeyup="applyBackupFilters()">
|
<input type="text" id="filter-keyword" aria-label="搜尋名稱或描述" placeholder="🔍 搜尋名稱或描述..." class="edit-input" style="width: 180px; padding: 6px 10px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px;" onkeyup="applyBackupFilters()">
|
||||||
|
|
||||||
<select id="filter-type" class="edit-input" style="width: 110px; padding: 6px 10px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px;" onchange="applyBackupFilters()">
|
<select id="filter-type" aria-label="選擇過濾類型" class="edit-input" style="width: 110px; padding: 6px 10px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px;" onchange="applyBackupFilters()">
|
||||||
<option value="">所有類型</option>
|
<option value="">所有類型</option>
|
||||||
<option value="running">running</option>
|
<option value="running">running</option>
|
||||||
<option value="full">full</option>
|
<option value="full">full</option>
|
||||||
|
|
@ -1192,9 +1192,9 @@ FILE: index.html
|
||||||
|
|
||||||
<!-- 💡 修正:回歸原生 type="date",交由系統決定語系顯示 -->
|
<!-- 💡 修正:回歸原生 type="date",交由系統決定語系顯示 -->
|
||||||
<div style="display: flex; align-items: center; gap: 5px; border-left: 1px solid #bdc3c7; padding-left: 10px; margin-left: 2px;">
|
<div style="display: flex; align-items: center; gap: 5px; border-left: 1px solid #bdc3c7; padding-left: 10px; margin-left: 2px;">
|
||||||
<input type="date" id="filter-date-start" class="edit-input" style="padding: 5px 8px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px; color: #7f8c8d;" onchange="applyBackupFilters()">
|
<input type="date" id="filter-date-start" aria-label="開始日期" class="edit-input" style="padding: 5px 8px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px; color: #7f8c8d;" onchange="applyBackupFilters()">
|
||||||
<span style="color: #7f8c8d; font-size: 13px;">至</span>
|
<span style="color: #7f8c8d; font-size: 13px;">至</span>
|
||||||
<input type="date" id="filter-date-end" class="edit-input" style="padding: 5px 8px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px; color: #7f8c8d;" onchange="applyBackupFilters()">
|
<input type="date" id="filter-date-end" aria-label="結束日期" class="edit-input" style="padding: 5px 8px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px; color: #7f8c8d;" onchange="applyBackupFilters()">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onclick="loadBackupHistory()" class="btn-modern btn-slate" style="margin-left: 5px; padding: 8px 20px; font-size: 14px;">
|
<button onclick="loadBackupHistory()" class="btn-modern btn-slate" style="margin-left: 5px; padding: 8px 20px; font-size: 14px;">
|
||||||
|
|
@ -1767,6 +1767,7 @@ from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware # 🌟 新增引入
|
||||||
import database
|
import database
|
||||||
|
|
||||||
# 引入我們剛剛拆分出來的路由模組
|
# 引入我們剛剛拆分出來的路由模組
|
||||||
|
|
@ -1782,6 +1783,26 @@ async def lifespan(app: FastAPI):
|
||||||
|
|
||||||
app = FastAPI(title="Harmonic CMTS Manager", version="2.0", lifespan=lifespan)
|
app = FastAPI(title="Harmonic CMTS Manager", version="2.0", lifespan=lifespan)
|
||||||
|
|
||||||
|
# 🌟 新增:全域安全與快取 Middleware
|
||||||
|
class SecurityAndCacheMiddleware(BaseHTTPMiddleware):
|
||||||
|
async def dispatch(self, request, call_next):
|
||||||
|
response = await call_next(request)
|
||||||
|
|
||||||
|
# 1. 補上安全性標頭 (圖片 7 的警告)
|
||||||
|
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||||
|
|
||||||
|
# 2. 針對靜態檔案補上 Cache-Control (圖片 2 的警告)
|
||||||
|
# 讓 JS/CSS 快取 1 小時 (3600秒),提升前端載入效能
|
||||||
|
if request.url.path.startswith("/static/"):
|
||||||
|
response.headers["Cache-Control"] = "public, max-age=3600"
|
||||||
|
else:
|
||||||
|
# API 請求不快取,確保拿到最新資料
|
||||||
|
response.headers["Cache-Control"] = "no-store"
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
app.add_middleware(SecurityAndCacheMiddleware) # 🌟 掛載 Middleware
|
||||||
|
|
||||||
# 掛載靜態檔案目錄 (對應 static/style.css 與 static/app.js)
|
# 掛載靜態檔案目錄 (對應 static/style.css 與 static/app.js)
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
|
|
@ -2701,19 +2722,26 @@ function toggleQueryInputs(mode) {
|
||||||
function resetAllManagerData() {
|
function resetAllManagerData() {
|
||||||
resetMacDomainData();
|
resetMacDomainData();
|
||||||
|
|
||||||
// 🌟 新增:清空動態下拉選單的快取,確保切換設備時重新抓取
|
// 🧹 1. 清空動態下拉選單的快取
|
||||||
if (typeof cachedDeviceLists !== 'undefined') {
|
if (typeof cachedDeviceLists !== 'undefined') {
|
||||||
cachedDeviceLists = { cms: null, rpds: null, host: null };
|
cachedDeviceLists = { cms: null, rpds: null, host: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧹 [修復 Leak] 清空前端樹狀圖快取,避免切換設備時發生 OOM
|
// 🧹 2. 清空前端樹狀圖快取與 DOM (設備配置樹狀圖)
|
||||||
clearTreeCache();
|
clearTreeCache();
|
||||||
|
window.treeDataStore = { running: null, full: null }; // 🌟 清空雙軌記憶體
|
||||||
|
|
||||||
// 🛡️ [跨設備防護] 切換設備時,清空鎖定輪詢的記憶體
|
const treeRunning = document.getElementById('tree-container-running');
|
||||||
|
const treeFull = document.getElementById('tree-container-full');
|
||||||
|
if (treeRunning) treeRunning.innerHTML = '<span style="color: #7f8c8d;">尚未載入 Running 資料。請點擊上方「載入任務」按鈕開始。</span>';
|
||||||
|
if (treeFull) treeFull.innerHTML = '<span style="color: #7f8c8d;">尚未載入 Full 資料。請點擊上方「載入任務」按鈕開始。</span>';
|
||||||
|
|
||||||
|
// 🛡️ 3. 跨設備防護:清空鎖定輪詢的記憶體
|
||||||
if (typeof activeLockState !== 'undefined' && activeLockState.clear) {
|
if (typeof activeLockState !== 'undefined' && activeLockState.clear) {
|
||||||
activeLockState.clear();
|
activeLockState.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🧹 4. 清空 MAC Domain 配置精靈
|
||||||
const configArea = document.getElementById('macDomainConfigArea');
|
const configArea = document.getElementById('macDomainConfigArea');
|
||||||
if (configArea) configArea.style.display = 'none';
|
if (configArea) configArea.style.display = 'none';
|
||||||
|
|
||||||
|
|
@ -2743,10 +2771,39 @@ function resetAllManagerData() {
|
||||||
fetchStatus.style.color = "#7f8c8d";
|
fetchStatus.style.color = "#7f8c8d";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🧹 5. 清空 CMTS 狀態查詢輸入框
|
||||||
document.getElementById('queryTargetCm').value = '';
|
document.getElementById('queryTargetCm').value = '';
|
||||||
document.getElementById('queryTargetRpd').value = '';
|
document.getElementById('queryTargetRpd').value = '';
|
||||||
|
|
||||||
// 🌟 新增:切換設備連線後,自動重整備份歷史紀錄
|
// 🧹 6. 清空 CM 深度診斷與 MER 分析
|
||||||
|
const diagMacInput = document.getElementById('diagMacInput');
|
||||||
|
if (diagMacInput) diagMacInput.value = '';
|
||||||
|
|
||||||
|
const diagResultArea = document.getElementById('diagResultArea');
|
||||||
|
if (diagResultArea) diagResultArea.style.display = 'none';
|
||||||
|
|
||||||
|
if (typeof merChartInstance !== 'undefined' && merChartInstance) {
|
||||||
|
merChartInstance.destroy();
|
||||||
|
merChartInstance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🧹 7. 清空系統設定 (God Mode Filters) 的樹狀圖
|
||||||
|
const filterContainer = document.getElementById('filter-checkboxes');
|
||||||
|
if (filterContainer) {
|
||||||
|
filterContainer.innerHTML = '';
|
||||||
|
filterContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🧹 8. 清空設備備份與還原歷史紀錄
|
||||||
|
if (typeof allBackupsData !== 'undefined') {
|
||||||
|
allBackupsData = []; // 清空記憶體中的快照陣列
|
||||||
|
}
|
||||||
|
const backupTbody = document.getElementById('backup-history-tbody');
|
||||||
|
if (backupTbody) {
|
||||||
|
backupTbody.innerHTML = '<tr><td colspan="5" style="padding: 20px; text-align: center; color: #7f8c8d;">請點擊「重新整理」載入歷史紀錄...</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🌟 觸發重新載入備份歷史 (因為已經連上新設備)
|
||||||
if (typeof loadBackupHistory === 'function') {
|
if (typeof loadBackupHistory === 'function') {
|
||||||
loadBackupHistory();
|
loadBackupHistory();
|
||||||
}
|
}
|
||||||
|
|
@ -4467,6 +4524,11 @@ export function connectWebSocket(resetCallback) {
|
||||||
|
|
||||||
if (resetCallback) resetCallback(); // 呼叫 app.js 傳進來的重置函數
|
if (resetCallback) resetCallback(); // 呼叫 app.js 傳進來的重置函數
|
||||||
|
|
||||||
|
// 🌟 [修復] 切換設備時,徹底清空終端機舊有畫面與緩衝區
|
||||||
|
if (term) {
|
||||||
|
term.clear();
|
||||||
|
}
|
||||||
|
|
||||||
term.writeln(`\x1b[33mConnecting to ${connInfo.host} via WebSocket...\x1b[0m`);
|
term.writeln(`\x1b[33mConnecting to ${connInfo.host} via WebSocket...\x1b[0m`);
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const wsUrl = `${protocol}//${window.location.host}/ws/terminal?host=${encodeURIComponent(connInfo.host)}&username=${encodeURIComponent(connInfo.user)}&password=${encodeURIComponent(connInfo.pass)}`;
|
const wsUrl = `${protocol}//${window.location.host}/ws/terminal?host=${encodeURIComponent(connInfo.host)}&username=${encodeURIComponent(connInfo.user)}&password=${encodeURIComponent(connInfo.pass)}`;
|
||||||
|
|
@ -4724,7 +4786,7 @@ button:hover { background-color: #3498db; }
|
||||||
.form-actions button { padding: 8px 20px; font-size: 14px; min-width: 120px; }
|
.form-actions button { padding: 8px 20px; font-size: 14px; min-width: 120px; }
|
||||||
|
|
||||||
/* 8. 彈出式輸出視窗 (Modal) 樣式 */
|
/* 8. 彈出式輸出視窗 (Modal) 樣式 */
|
||||||
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1000; align-items: center; justify-content: center; backdrop-filter: blur(3px); }
|
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1000; align-items: center; justify-content: center; -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); } /* 🌟 新增這行支援 Safari */
|
||||||
.modal-overlay.active { display: flex; animation: fadeIn 0.2s ease-out; }
|
.modal-overlay.active { display: flex; animation: fadeIn 0.2s ease-out; }
|
||||||
.modal-container { background: #1e1e1e; width: 95vw; max-width: 1800px; height: 85vh; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 15px 40px rgba(0,0,0,0.6); transform: translateY(20px); transition: transform 0.3s ease-out; }
|
.modal-container { background: #1e1e1e; width: 95vw; max-width: 1800px; height: 85vh; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 15px 40px rgba(0,0,0,0.6); transform: translateY(20px); transition: transform 0.3s ease-out; }
|
||||||
.modal-overlay.active .modal-container { transform: translateY(0); }
|
.modal-overlay.active .modal-container { transform: translateY(0); }
|
||||||
|
|
@ -4765,6 +4827,7 @@ button:hover { background-color: #3498db; }
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
|
-webkit-user-select: none; /* 🌟 新增這行支援 Safari */
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: #2c3e50; /* 保持原本的深色文字 */
|
color: #2c3e50; /* 保持原本的深色文字 */
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
|
@ -6856,7 +6919,11 @@ window.forceRenderFolderHTML = function(elementId) {
|
||||||
const contentDiv = document.getElementById(`content-${elementId}`);
|
const contentDiv = document.getElementById(`content-${elementId}`);
|
||||||
const cache = folderDataCache[elementId];
|
const cache = folderDataCache[elementId];
|
||||||
|
|
||||||
if (detailsEl && contentDiv && cache && detailsEl.dataset.loaded !== 'true') {
|
// 🌟 關鍵修復:移除 detailsEl.dataset.loaded !== 'true' 的限制!
|
||||||
|
// 因為如果使用者先手動展開了父資料夾,再點擊編輯,
|
||||||
|
// 原本的邏輯會因為 loaded === true 而拒絕往下遞迴渲染子資料夾,
|
||||||
|
// 導致子資料夾內的項目沒有被轉成輸入框。
|
||||||
|
if (detailsEl && contentDiv && cache) {
|
||||||
const currentPath = detailsEl.dataset.path;
|
const currentPath = detailsEl.dataset.path;
|
||||||
const isCommandGroup = detailsEl.dataset.isGroup === 'true';
|
const isCommandGroup = detailsEl.dataset.isGroup === 'true';
|
||||||
const mode = detailsEl.dataset.mode;
|
const mode = detailsEl.dataset.mode;
|
||||||
|
|
|
||||||
88
index.html
88
index.html
|
|
@ -21,9 +21,9 @@
|
||||||
<div class="global-settings">
|
<div class="global-settings">
|
||||||
<label><strong>🌐 目標設備:</strong></label>
|
<label><strong>🌐 目標設備:</strong></label>
|
||||||
<!-- 🌟 修改後 (徹底淨化) -->
|
<!-- 🌟 修改後 (徹底淨化) -->
|
||||||
<input type="text" id="cmtsHost" placeholder="IP 地址" style="width: 130px;">
|
<input type="text" id="cmtsHost" aria-label="IP 地址" placeholder="IP 地址" style="width: 130px;">
|
||||||
<input type="text" id="cmtsUser" placeholder="帳號" style="width: 100px;">
|
<input type="text" id="cmtsUser" aria-label="帳號" placeholder="帳號" style="width: 100px;">
|
||||||
<input type="password" id="cmtsPass" placeholder="密碼" style="width: 100px;">
|
<input type="password" id="cmtsPass" aria-label="密碼" placeholder="密碼" style="width: 100px;">
|
||||||
|
|
||||||
<div style="margin-left: 15px; display: flex; align-items: center; gap: 10px;">
|
<div style="margin-left: 15px; display: flex; align-items: center; gap: 10px;">
|
||||||
<button onclick="connectWebSocket()" id="btnConnect" class="btn-modern btn-connect">連線至 CMTS</button>
|
<button onclick="connectWebSocket()" id="btnConnect" class="btn-modern btn-connect">連線至 CMTS</button>
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
<div id="cli-tab" class="tab-content active">
|
<div id="cli-tab" class="tab-content active">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label><strong>快捷指令:</strong></label>
|
<label><strong>快捷指令:</strong></label>
|
||||||
<select id="fixedCmd">
|
<select id="fixedCmd" aria-label="選擇快捷指令" >
|
||||||
<option value="show cable modem | nomore">數據機狀態 (show cable modem | nomore)</option>
|
<option value="show cable modem | nomore">數據機狀態 (show cable modem | nomore)</option>
|
||||||
<option value="show cable rpd | nomore">RPD 狀態 (show cable rpd | nomore)</option>
|
<option value="show cable rpd | nomore">RPD 狀態 (show cable rpd | nomore)</option>
|
||||||
<option value="show running-config | nomore">系統實時設定檔 (show running-config | nomore)</option>
|
<option value="show running-config | nomore">系統實時設定檔 (show running-config | nomore)</option>
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
|
|
||||||
<div class="control-group" style="background: #ffffff; padding: 15px 25px; border-radius: 8px; margin-bottom: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); border: 1px solid #e2e8f0;">
|
<div class="control-group" style="background: #ffffff; padding: 15px 25px; border-radius: 8px; margin-bottom: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); border: 1px solid #e2e8f0;">
|
||||||
<label style="font-weight: bold; color: #2c3e50; font-size: 16px; margin-right: 10px;">📌 選擇查詢任務:</label>
|
<label style="font-weight: bold; color: #2c3e50; font-size: 16px; margin-right: 10px;">📌 選擇查詢任務:</label>
|
||||||
<select id="queryTask" onchange="switchQueryTask()" style="width: 400px; max-width: 100%; font-weight: bold; font-size: 15px; padding: 8px; background-color: #f8f9fa;">
|
<select id="queryTask" onchange="switchQueryTask()" aria-label="選擇查詢任務" style="width: 400px; max-width: 100%; font-weight: bold; font-size: 15px; padding: 8px; background-color: #f8f9fa;">
|
||||||
<option value="form-cm-query">🔎 Cable 狀態綜合查詢</option>
|
<option value="form-cm-query">🔎 Cable 狀態綜合查詢</option>
|
||||||
<option value="form-rpd-query">📡 RPD 狀態綜合查詢</option>
|
<option value="form-rpd-query">📡 RPD 狀態綜合查詢</option>
|
||||||
<option value="form-cm-diagnostics">🩺 CM 一鍵診斷中心</option>
|
<option value="form-cm-diagnostics">🩺 CM 一鍵診斷中心</option>
|
||||||
|
|
@ -79,13 +79,13 @@
|
||||||
<h3 style="margin-top: 0; font-size: 18px; color: #2980b9; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">Cable 狀態綜合查詢 (show cable modem...)</h3>
|
<h3 style="margin-top: 0; font-size: 18px; color: #2980b9; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">Cable 狀態綜合查詢 (show cable modem...)</h3>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>目標設備 (Cable Modem MAC)</label>
|
<label for="queryTargetCm">目標設備 (Cable Modem MAC)</label>
|
||||||
<input type="text" id="queryTargetCm" list="cm-mac-list" placeholder="例: 6467.7240.4076 (留白則查詢全體)">
|
<input type="text" id="queryTargetCm" list="cm-mac-list" placeholder="例: 6467.7240.4076 (留白則查詢全體)">
|
||||||
<datalist id="cm-mac-list"></datalist>
|
<datalist id="cm-mac-list"></datalist>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>查詢動作 (Action)</label>
|
<label>查詢動作 (Action)</label>
|
||||||
<select id="queryTypeCm" onchange="toggleQueryInputs('Cm')">
|
<select id="queryTypeCm" onchange="toggleQueryInputs('Cm')" aria-label="選擇 Cable Modem 查詢動作">
|
||||||
<optgroup label="Cable Modem 查詢 (支援特定目標或全體)">
|
<optgroup label="Cable Modem 查詢 (支援特定目標或全體)">
|
||||||
<option value="base">基本狀態 (show cable modem)</option>
|
<option value="base">基本狀態 (show cable modem)</option>
|
||||||
<option value="cpe">CPE 資訊 (cpe)</option>
|
<option value="cpe">CPE 資訊 (cpe)</option>
|
||||||
|
|
@ -129,13 +129,13 @@
|
||||||
<h3 style="margin-top: 0; font-size: 18px; color: #e67e22; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">RPD 狀態綜合查詢 (show cable rpd...)</h3>
|
<h3 style="margin-top: 0; font-size: 18px; color: #e67e22; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">RPD 狀態綜合查詢 (show cable rpd...)</h3>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>目標設備 (RPD VC:VS)</label>
|
<label for="queryTargetRpd">目標設備 (RPD VC:VS)</label>
|
||||||
<input type="text" id="queryTargetRpd" list="rpd-vcvs-list" placeholder="例: 13:0 (留白則查詢全體)">
|
<input type="text" id="queryTargetRpd" list="rpd-vcvs-list" placeholder="例: 13:0 (留白則查詢全體)">
|
||||||
<datalist id="rpd-vcvs-list"></datalist>
|
<datalist id="rpd-vcvs-list"></datalist>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>查詢動作 (Action)</label>
|
<label>查詢動作 (Action)</label>
|
||||||
<select id="queryTypeRpd" onchange="toggleQueryInputs('Rpd')">
|
<select id="queryTypeRpd" onchange="toggleQueryInputs('Rpd')" aria-label="選擇 RPD 查詢動作">
|
||||||
<option value="rpd_base">基本狀態 (show cable rpd)</option>
|
<option value="rpd_base">基本狀態 (show cable rpd)</option>
|
||||||
<option value="rpd_verbose">詳細資訊 (verbose)</option>
|
<option value="rpd_verbose">詳細資訊 (verbose)</option>
|
||||||
<option value="rpd_ptp_time">PTP Time Property (ptp time-property)</option>
|
<option value="rpd_ptp_time">PTP Time Property (ptp time-property)</option>
|
||||||
|
|
@ -166,7 +166,7 @@
|
||||||
|
|
||||||
<!-- 搜尋區塊 -->
|
<!-- 搜尋區塊 -->
|
||||||
<div class="control-group" style="background: #fdfefe; padding: 15px; border-radius: 6px; border: 1px solid #e8daef; margin-bottom: 20px;">
|
<div class="control-group" style="background: #fdfefe; padding: 15px; border-radius: 6px; border: 1px solid #e8daef; margin-bottom: 20px;">
|
||||||
<label style="font-weight: bold; color: #2c3e50; font-size: 15px;">🎯 目標 CM MAC:</label>
|
<label for="diagMacInput" style="font-weight: bold; color: #2c3e50; font-size: 15px;">🎯 目標 CM MAC:</label>
|
||||||
<input type="text" id="diagMacInput" list="diag-cm-mac-list" placeholder="例如: 6467.7240.4076" style="width: 200px; padding: 6px 8px; font-size: 14px; margin: 0 10px; border: 1px solid #bdc3c7; border-radius: 4px;">
|
<input type="text" id="diagMacInput" list="diag-cm-mac-list" placeholder="例如: 6467.7240.4076" style="width: 200px; padding: 6px 8px; font-size: 14px; margin: 0 10px; border: 1px solid #bdc3c7; border-radius: 4px;">
|
||||||
<datalist id="diag-cm-mac-list"></datalist>
|
<datalist id="diag-cm-mac-list"></datalist>
|
||||||
<button onclick="runCmDiagnostics()" id="btnRunDiag" class="btn-modern btn-scan" style="background-color: #8e44ad;">🚀 執行深度診斷</button>
|
<button onclick="runCmDiagnostics()" id="btnRunDiag" class="btn-modern btn-scan" style="background-color: #8e44ad;">🚀 執行深度診斷</button>
|
||||||
|
|
@ -237,7 +237,7 @@
|
||||||
|
|
||||||
<div class="control-group" style="background: #ffffff; padding: 10px 15px; border-radius: 8px; margin-bottom: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); border: 1px solid #e2e8f0;">
|
<div class="control-group" style="background: #ffffff; padding: 10px 15px; border-radius: 8px; margin-bottom: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); border: 1px solid #e2e8f0;">
|
||||||
<label style="font-weight: bold; color: #c0392b; font-size: 16px; margin-right: 10px;">📌 選擇配置任務:</label>
|
<label style="font-weight: bold; color: #c0392b; font-size: 16px; margin-right: 10px;">📌 選擇配置任務:</label>
|
||||||
<select id="configTask" onchange="switchConfigTask()" style="width: 400px; max-width: 100%; font-weight: bold; font-size: 15px; padding: 8px; background-color: #fcf3f2; border-color: #fadbd8;">
|
<select id="configTask" aria-label="選擇配置任務" onchange="switchConfigTask()" style="width: 400px; max-width: 100%; font-weight: bold; font-size: 15px; padding: 8px; background-color: #fcf3f2; border-color: #fadbd8;">
|
||||||
<!-- 🌟 拆分為兩個獨立的選項 -->
|
<!-- 🌟 拆分為兩個獨立的選項 -->
|
||||||
<option value="form-running-config">🌳 設備配置樹狀圖 (running-config)</option>
|
<option value="form-running-config">🌳 設備配置樹狀圖 (running-config)</option>
|
||||||
<option value="form-full-config">🌳 完整設備配置樹狀圖 (full configuration)</option>
|
<option value="form-full-config">🌳 完整設備配置樹狀圖 (full configuration)</option>
|
||||||
|
|
@ -251,7 +251,7 @@
|
||||||
<h3 style="margin-top: 0; font-size: 18px; color: #c0392b; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">⚠️ MAC Domain 狀態感知配置精靈</h3>
|
<h3 style="margin-top: 0; font-size: 18px; color: #c0392b; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 20px;">⚠️ MAC Domain 狀態感知配置精靈</h3>
|
||||||
|
|
||||||
<div class="control-group" style="background: #fdf2e9; padding: 15px; border-radius: 6px; border: 1px solid #fadbd8;">
|
<div class="control-group" style="background: #fdf2e9; padding: 15px; border-radius: 6px; border: 1px solid #fadbd8;">
|
||||||
<label style="font-weight: bold; color: #d35400;">1. 目標 MAC Domain:</label>
|
<label for="cfgMacDomain" style="font-weight: bold; color: #d35400;">1. 目標 MAC Domain:</label>
|
||||||
<input type="text" id="cfgMacDomain" list="mac-domain-list" placeholder="請先點擊上方載入任務..." style="width: 220px; padding: 6px; font-weight: bold; border: 1px solid #fadbd8; border-radius: 4px; background-color: #fff;">
|
<input type="text" id="cfgMacDomain" list="mac-domain-list" placeholder="請先點擊上方載入任務..." style="width: 220px; padding: 6px; font-weight: bold; border: 1px solid #fadbd8; border-radius: 4px; background-color: #fff;">
|
||||||
<datalist id="mac-domain-list"></datalist>
|
<datalist id="mac-domain-list"></datalist>
|
||||||
<button onclick="fetchMacDomainConfig()" class="btn-modern btn-load">🔍 讀取現有配置</button>
|
<button onclick="fetchMacDomainConfig()" class="btn-modern btn-load">🔍 讀取現有配置</button>
|
||||||
|
|
@ -262,39 +262,39 @@
|
||||||
<!-- Common Settings -->
|
<!-- Common Settings -->
|
||||||
<h4 style="color: #2980b9; border-left: 4px solid #2980b9; padding-left: 8px;">Common Settings</h4>
|
<h4 style="color: #2980b9; border-left: 4px solid #2980b9; padding-left: 8px;">Common Settings</h4>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row"><label>IP Provisioning Mode</label><select id="g_ip_prov"><option value="alternate">alternate</option><option value="dual-stack">dual-stack</option><option value="ipv4-only">ipv4-only</option><option value="ipv6-only">ipv6-only</option></select></div>
|
<div class="form-row"><label for="g_ip_prov">IP Provisioning Mode</label><select id="g_ip_prov"><option value="alternate">alternate</option><option value="dual-stack">dual-stack</option><option value="ipv4-only">ipv4-only</option><option value="ipv6-only">ipv6-only</option></select></div>
|
||||||
<div class="form-row"><label>Diplexer Band Edge Control</label><select id="g_diplexer"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_diplexer">Diplexer Band Edge Control</label><select id="g_diplexer"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>CM Battery Mode 3.1</label><select id="g_bat31"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_bat31">CM Battery Mode 3.1</label><select id="g_bat31"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>CM Battery Mode 3.0</label><select id="g_bat30"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_bat30">CM Battery Mode 3.0</label><select id="g_bat30"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>DOCSIS 4.0</label><select id="g_docsis40"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_docsis40">DOCSIS 4.0</label><select id="g_docsis40"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>DS Dynamic Bonding Group</label><select id="g_ds_dyn" onchange="toggleGroupVisibility()"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_ds_dyn">DS Dynamic Bonding Group</label><select id="g_ds_dyn" onchange="toggleGroupVisibility()"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
<div class="form-row"><label>US Dynamic Bonding Group</label><select id="g_us_dyn" onchange="toggleGroupVisibility()"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
<div class="form-row"><label for="g_us_dyn">US Dynamic Bonding Group</label><select id="g_us_dyn" onchange="toggleGroupVisibility()"><option value="enabled">enabled</option><option value="disabled">disabled</option></select></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- [Basic] DS/US Channel Sets -->
|
<!-- [Basic] DS/US Channel Sets -->
|
||||||
<h4 style="color: #27ae60; border-left: 4px solid #27ae60; padding-left: 8px; margin-top: 30px;">[Basic] DS/US Channel Sets</h4>
|
<h4 style="color: #27ae60; border-left: 4px solid #27ae60; padding-left: 8px; margin-top: 30px;">[Basic] DS/US Channel Sets</h4>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row"><label>Admin State</label><select id="b_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
<div class="form-row"><label for="b_admin">Admin State</label><select id="b_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
||||||
<div class="form-row"><label>DS Primary Set (0..157)</label><input type="text" id="b_ds_pri" placeholder="例: 0-2"></div>
|
<div class="form-row"><label for="b_ds_pri">DS Primary Set (0..157)</label><input type="text" id="b_ds_pri" placeholder="例: 0-2"></div>
|
||||||
<div class="form-row"><label>DS Non-Primary Set (0..157)</label><input type="text" id="b_ds_non_pri" placeholder="例: 3-4"></div>
|
<div class="form-row"><label for="b_ds_non_pri">DS Non-Primary Set (0..157)</label><input type="text" id="b_ds_non_pri" placeholder="例: 3-4"></div>
|
||||||
<div class="form-row"><label>US PHY Channel Set (0..255)</label><input type="text" id="b_us_phy" placeholder="例: 0-3"></div>
|
<div class="form-row"><label for="b_us_phy">US PHY Channel Set (0..255)</label><input type="text" id="b_us_phy" placeholder="例: 0-3"></div>
|
||||||
<div class="form-row"><label>DS OFDM Set (0..7)</label><input type="text" id="b_ds_ofdm" placeholder="例: 0"></div>
|
<div class="form-row"><label for="b_ds_ofdm">DS OFDM Set (0..7)</label><input type="text" id="b_ds_ofdm" placeholder="例: 0"></div>
|
||||||
<div class="form-row"><label>US OFDMA Set (0..1)</label><input type="text" id="b_us_ofdma" placeholder="例: 0"></div>
|
<div class="form-row"><label for="b_us_ofdma">US OFDMA Set (0..1)</label><input type="text" id="b_us_ofdma" placeholder="例: 0"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- [Static] Downstream Bonding Groups -->
|
<!-- [Static] Downstream Bonding Groups -->
|
||||||
<div id="section_group_ds" style="margin-top: 30px;">
|
<div id="section_group_ds" style="margin-top: 30px;">
|
||||||
<h4 style="color: #8e44ad; border-left: 4px solid #8e44ad; padding-left: 8px;">[Static] Downstream Bonding Groups</h4>
|
<h4 style="color: #8e44ad; border-left: 4px solid #8e44ad; padding-left: 8px;">[Static] Downstream Bonding Groups</h4>
|
||||||
<div class="control-group" style="background: #f4f6f7; padding: 10px; border-radius: 4px;">
|
<div class="control-group" style="background: #f4f6f7; padding: 10px; border-radius: 4px;">
|
||||||
<label>選擇要編輯的 DS Group:</label>
|
<label for="select_ds_group">選擇要編輯的 DS Group:</label>
|
||||||
<select id="select_ds_group" onchange="loadDsGroupData()" style="width: 200px;"></select>
|
<select id="select_ds_group" onchange="loadDsGroupData()" style="width: 200px;"></select>
|
||||||
<input type="text" id="input_new_ds_group" placeholder="輸入新 Group 名稱 (例: D4A)" style="display: none; width: 200px;">
|
<input type="text" id="input_new_ds_group" aria-label="輸入新 DS Group 名稱" placeholder="輸入新 Group 名稱 (例: D4A)" style="display: none; width: 200px;">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row"><label>Admin State</label><select id="ds_g_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
<div class="form-row"><label for="ds_g_admin">Admin State</label><select id="ds_g_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
||||||
<div class="form-row"><label>Down Channel Set (0..157)</label><input type="text" id="ds_g_down" placeholder="例: 0-4"></div>
|
<div class="form-row"><label for="ds_g_down">Down Channel Set (0..157)</label><input type="text" id="ds_g_down" placeholder="例: 0-4"></div>
|
||||||
<div class="form-row"><label>OFDM Channel Set (0..7)</label><input type="text" id="ds_g_ofdm" placeholder="例: 0-1"></div>
|
<div class="form-row"><label for="ds_g_ofdm">OFDM Channel Set (0..7)</label><input type="text" id="ds_g_ofdm" placeholder="例: 0-1"></div>
|
||||||
<div class="form-row"><label>FDX OFDM Channel Set (0..7)</label><input type="text" id="ds_g_fdx" placeholder="例: 0-2"></div>
|
<div class="form-row"><label for="ds_g_fdx">FDX OFDM Channel Set (0..7)</label><input type="text" id="ds_g_fdx" placeholder="例: 0-2"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -302,15 +302,15 @@
|
||||||
<div id="section_group_us" style="margin-top: 30px;">
|
<div id="section_group_us" style="margin-top: 30px;">
|
||||||
<h4 style="color: #f39c12; border-left: 4px solid #f39c12; padding-left: 8px;">[Static] Upstream Bonding Groups</h4>
|
<h4 style="color: #f39c12; border-left: 4px solid #f39c12; padding-left: 8px;">[Static] Upstream Bonding Groups</h4>
|
||||||
<div class="control-group" style="background: #f4f6f7; padding: 10px; border-radius: 4px;">
|
<div class="control-group" style="background: #f4f6f7; padding: 10px; border-radius: 4px;">
|
||||||
<label>選擇要編輯的 US Group:</label>
|
<label for="select_us_group">選擇要編輯的 US Group:</label>
|
||||||
<select id="select_us_group" onchange="loadUsGroupData()" style="width: 200px;"></select>
|
<select id="select_us_group" onchange="loadUsGroupData()" style="width: 200px;"></select>
|
||||||
<input type="text" id="input_new_us_group" placeholder="輸入新 Group 名稱 (例: U4A)" style="display: none; width: 200px;">
|
<input type="text" id="input_new_us_group" aria-label="輸入新 US Group 名稱" placeholder="輸入新 Group 名稱 (例: U4A)" style="display: none; width: 200px;">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-row"><label>Admin State</label><select id="us_g_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
<div class="form-row"><label for="us_g_admin">Admin State</label><select id="us_g_admin"><option value="up">up</option><option value="down">down</option></select></div>
|
||||||
<div class="form-row"><label>US Channel Set</label><input type="text" id="us_g_us" placeholder="例: 0-3.0"></div>
|
<div class="form-row"><label for="us_g_us">US Channel Set</label><input type="text" id="us_g_us" placeholder="例: 0-3.0"></div>
|
||||||
<div class="form-row"><label>OFDMA Channel Set (0..1)</label><input type="text" id="us_g_ofdma" placeholder="例: 0"></div>
|
<div class="form-row"><label for="us_g_ofdma">OFDMA Channel Set (0..1)</label><input type="text" id="us_g_ofdma" placeholder="例: 0"></div>
|
||||||
<div class="form-row"><label>FDX OFDMA Channel Set (0..5)</label><input type="text" id="us_g_fdx" placeholder="例: 0-5"></div>
|
<div class="form-row"><label for="us_g_fdx">FDX OFDMA Channel Set (0..5)</label><input type="text" id="us_g_fdx" placeholder="例: 0-5"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -407,7 +407,7 @@
|
||||||
|
|
||||||
<!-- 🌟 新增:過濾器模式切換下拉選單 -->
|
<!-- 🌟 新增:過濾器模式切換下拉選單 -->
|
||||||
<div style="margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
|
<div style="margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
|
||||||
<label style="font-weight: bold; color: #2c3e50;">🎯 選擇要編輯的過濾器:</label>
|
<label for="filter-mode-select" style="font-weight: bold; color: #2c3e50;">🎯 選擇要編輯的過濾器:</label>
|
||||||
<select id="filter-mode-select" onchange="switchFilterMode()" style="padding: 6px 10px; border-radius: 4px; border: 1px solid #bdc3c7; font-weight: bold; font-size: 14px; background-color: #fcf3f2; color: #c0392b;">
|
<select id="filter-mode-select" onchange="switchFilterMode()" style="padding: 6px 10px; border-radius: 4px; border: 1px solid #bdc3c7; font-weight: bold; font-size: 14px; background-color: #fcf3f2; color: #c0392b;">
|
||||||
<option value="running">Running 配置過濾器</option>
|
<option value="running">Running 配置過濾器</option>
|
||||||
<option value="full">Full 配置過濾器</option>
|
<option value="full">Full 配置過濾器</option>
|
||||||
|
|
@ -477,13 +477,13 @@
|
||||||
<!-- 表單網格區 -->
|
<!-- 表單網格區 -->
|
||||||
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 15px;">
|
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 15px;">
|
||||||
<div>
|
<div>
|
||||||
<label style="display: block; font-size: 14px; font-weight: bold; color: #34495e; margin-bottom: 8px;">
|
<label for="snapshotName" style="display: block; font-size: 14px; font-weight: bold; color: #34495e; margin-bottom: 8px;">
|
||||||
快照名稱 <span style="color: #e74c3c;">*</span>
|
快照名稱 <span style="color: #e74c3c;">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="snapshotName" placeholder="例如: 例行備份、升級前備份" style="width: 100%; padding: 10px 12px; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 14px; transition: border-color 0.2s;">
|
<input type="text" id="snapshotName" placeholder="例如: 例行備份、升級前備份" style="width: 100%; padding: 10px 12px; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 14px; transition: border-color 0.2s;">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style="display: block; font-size: 14px; font-weight: bold; color: #34495e; margin-bottom: 8px;">
|
<label for="backupConfigType" style="display: block; font-size: 14px; font-weight: bold; color: #34495e; margin-bottom: 8px;">
|
||||||
配置類型
|
配置類型
|
||||||
</label>
|
</label>
|
||||||
<select id="backupConfigType" style="width: 100%; padding: 10px 12px; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 14px; background-color: #f8f9fa; cursor: pointer;">
|
<select id="backupConfigType" style="width: 100%; padding: 10px 12px; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 14px; background-color: #f8f9fa; cursor: pointer;">
|
||||||
|
|
@ -513,9 +513,9 @@
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div style="display: flex; gap: 10px; align-items: center;">
|
<div style="display: flex; gap: 10px; align-items: center;">
|
||||||
<input type="text" id="filter-keyword" placeholder="🔍 搜尋名稱或描述..." class="edit-input" style="width: 180px; padding: 6px 10px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px;" onkeyup="applyBackupFilters()">
|
<input type="text" id="filter-keyword" aria-label="搜尋名稱或描述" placeholder="🔍 搜尋名稱或描述..." class="edit-input" style="width: 180px; padding: 6px 10px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px;" onkeyup="applyBackupFilters()">
|
||||||
|
|
||||||
<select id="filter-type" class="edit-input" style="width: 110px; padding: 6px 10px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px;" onchange="applyBackupFilters()">
|
<select id="filter-type" aria-label="選擇過濾類型" class="edit-input" style="width: 110px; padding: 6px 10px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px;" onchange="applyBackupFilters()">
|
||||||
<option value="">所有類型</option>
|
<option value="">所有類型</option>
|
||||||
<option value="running">running</option>
|
<option value="running">running</option>
|
||||||
<option value="full">full</option>
|
<option value="full">full</option>
|
||||||
|
|
@ -523,9 +523,9 @@
|
||||||
|
|
||||||
<!-- 💡 修正:回歸原生 type="date",交由系統決定語系顯示 -->
|
<!-- 💡 修正:回歸原生 type="date",交由系統決定語系顯示 -->
|
||||||
<div style="display: flex; align-items: center; gap: 5px; border-left: 1px solid #bdc3c7; padding-left: 10px; margin-left: 2px;">
|
<div style="display: flex; align-items: center; gap: 5px; border-left: 1px solid #bdc3c7; padding-left: 10px; margin-left: 2px;">
|
||||||
<input type="date" id="filter-date-start" class="edit-input" style="padding: 5px 8px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px; color: #7f8c8d;" onchange="applyBackupFilters()">
|
<input type="date" id="filter-date-start" aria-label="開始日期" class="edit-input" style="padding: 5px 8px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px; color: #7f8c8d;" onchange="applyBackupFilters()">
|
||||||
<span style="color: #7f8c8d; font-size: 13px;">至</span>
|
<span style="color: #7f8c8d; font-size: 13px;">至</span>
|
||||||
<input type="date" id="filter-date-end" class="edit-input" style="padding: 5px 8px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px; color: #7f8c8d;" onchange="applyBackupFilters()">
|
<input type="date" id="filter-date-end" aria-label="結束日期" class="edit-input" style="padding: 5px 8px; border: 1px solid #bdc3c7; border-radius: 4px; font-size: 13px; color: #7f8c8d;" onchange="applyBackupFilters()">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onclick="loadBackupHistory()" class="btn-modern btn-slate" style="margin-left: 5px; padding: 8px 20px; font-size: 14px;">
|
<button onclick="loadBackupHistory()" class="btn-modern btn-slate" style="margin-left: 5px; padding: 8px 20px; font-size: 14px;">
|
||||||
|
|
|
||||||
21
main.py
21
main.py
|
|
@ -4,6 +4,7 @@ from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware # 🌟 新增引入
|
||||||
import database
|
import database
|
||||||
|
|
||||||
# 引入我們剛剛拆分出來的路由模組
|
# 引入我們剛剛拆分出來的路由模組
|
||||||
|
|
@ -19,6 +20,26 @@ async def lifespan(app: FastAPI):
|
||||||
|
|
||||||
app = FastAPI(title="Harmonic CMTS Manager", version="2.0", lifespan=lifespan)
|
app = FastAPI(title="Harmonic CMTS Manager", version="2.0", lifespan=lifespan)
|
||||||
|
|
||||||
|
# 🌟 新增:全域安全與快取 Middleware
|
||||||
|
class SecurityAndCacheMiddleware(BaseHTTPMiddleware):
|
||||||
|
async def dispatch(self, request, call_next):
|
||||||
|
response = await call_next(request)
|
||||||
|
|
||||||
|
# 1. 補上安全性標頭 (圖片 7 的警告)
|
||||||
|
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||||
|
|
||||||
|
# 2. 針對靜態檔案補上 Cache-Control (圖片 2 的警告)
|
||||||
|
# 讓 JS/CSS 快取 1 小時 (3600秒),提升前端載入效能
|
||||||
|
if request.url.path.startswith("/static/"):
|
||||||
|
response.headers["Cache-Control"] = "public, max-age=3600"
|
||||||
|
else:
|
||||||
|
# API 請求不快取,確保拿到最新資料
|
||||||
|
response.headers["Cache-Control"] = "no-store"
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
app.add_middleware(SecurityAndCacheMiddleware) # 🌟 掛載 Middleware
|
||||||
|
|
||||||
# 掛載靜態檔案目錄 (對應 static/style.css 與 static/app.js)
|
# 掛載靜態檔案目錄 (對應 static/style.css 與 static/app.js)
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -488,19 +488,26 @@ function toggleQueryInputs(mode) {
|
||||||
function resetAllManagerData() {
|
function resetAllManagerData() {
|
||||||
resetMacDomainData();
|
resetMacDomainData();
|
||||||
|
|
||||||
// 🌟 新增:清空動態下拉選單的快取,確保切換設備時重新抓取
|
// 🧹 1. 清空動態下拉選單的快取
|
||||||
if (typeof cachedDeviceLists !== 'undefined') {
|
if (typeof cachedDeviceLists !== 'undefined') {
|
||||||
cachedDeviceLists = { cms: null, rpds: null, host: null };
|
cachedDeviceLists = { cms: null, rpds: null, host: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧹 [修復 Leak] 清空前端樹狀圖快取,避免切換設備時發生 OOM
|
// 🧹 2. 清空前端樹狀圖快取與 DOM (設備配置樹狀圖)
|
||||||
clearTreeCache();
|
clearTreeCache();
|
||||||
|
window.treeDataStore = { running: null, full: null }; // 🌟 清空雙軌記憶體
|
||||||
|
|
||||||
// 🛡️ [跨設備防護] 切換設備時,清空鎖定輪詢的記憶體
|
const treeRunning = document.getElementById('tree-container-running');
|
||||||
|
const treeFull = document.getElementById('tree-container-full');
|
||||||
|
if (treeRunning) treeRunning.innerHTML = '<span style="color: #7f8c8d;">尚未載入 Running 資料。請點擊上方「載入任務」按鈕開始。</span>';
|
||||||
|
if (treeFull) treeFull.innerHTML = '<span style="color: #7f8c8d;">尚未載入 Full 資料。請點擊上方「載入任務」按鈕開始。</span>';
|
||||||
|
|
||||||
|
// 🛡️ 3. 跨設備防護:清空鎖定輪詢的記憶體
|
||||||
if (typeof activeLockState !== 'undefined' && activeLockState.clear) {
|
if (typeof activeLockState !== 'undefined' && activeLockState.clear) {
|
||||||
activeLockState.clear();
|
activeLockState.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🧹 4. 清空 MAC Domain 配置精靈
|
||||||
const configArea = document.getElementById('macDomainConfigArea');
|
const configArea = document.getElementById('macDomainConfigArea');
|
||||||
if (configArea) configArea.style.display = 'none';
|
if (configArea) configArea.style.display = 'none';
|
||||||
|
|
||||||
|
|
@ -530,10 +537,39 @@ function resetAllManagerData() {
|
||||||
fetchStatus.style.color = "#7f8c8d";
|
fetchStatus.style.color = "#7f8c8d";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🧹 5. 清空 CMTS 狀態查詢輸入框
|
||||||
document.getElementById('queryTargetCm').value = '';
|
document.getElementById('queryTargetCm').value = '';
|
||||||
document.getElementById('queryTargetRpd').value = '';
|
document.getElementById('queryTargetRpd').value = '';
|
||||||
|
|
||||||
// 🌟 新增:切換設備連線後,自動重整備份歷史紀錄
|
// 🧹 6. 清空 CM 深度診斷與 MER 分析
|
||||||
|
const diagMacInput = document.getElementById('diagMacInput');
|
||||||
|
if (diagMacInput) diagMacInput.value = '';
|
||||||
|
|
||||||
|
const diagResultArea = document.getElementById('diagResultArea');
|
||||||
|
if (diagResultArea) diagResultArea.style.display = 'none';
|
||||||
|
|
||||||
|
if (typeof merChartInstance !== 'undefined' && merChartInstance) {
|
||||||
|
merChartInstance.destroy();
|
||||||
|
merChartInstance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🧹 7. 清空系統設定 (God Mode Filters) 的樹狀圖
|
||||||
|
const filterContainer = document.getElementById('filter-checkboxes');
|
||||||
|
if (filterContainer) {
|
||||||
|
filterContainer.innerHTML = '';
|
||||||
|
filterContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🧹 8. 清空設備備份與還原歷史紀錄
|
||||||
|
if (typeof allBackupsData !== 'undefined') {
|
||||||
|
allBackupsData = []; // 清空記憶體中的快照陣列
|
||||||
|
}
|
||||||
|
const backupTbody = document.getElementById('backup-history-tbody');
|
||||||
|
if (backupTbody) {
|
||||||
|
backupTbody.innerHTML = '<tr><td colspan="5" style="padding: 20px; text-align: center; color: #7f8c8d;">請點擊「重新整理」載入歷史紀錄...</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🌟 觸發重新載入備份歷史 (因為已經連上新設備)
|
||||||
if (typeof loadBackupHistory === 'function') {
|
if (typeof loadBackupHistory === 'function') {
|
||||||
loadBackupHistory();
|
loadBackupHistory();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ button:hover { background-color: #3498db; }
|
||||||
.form-actions button { padding: 8px 20px; font-size: 14px; min-width: 120px; }
|
.form-actions button { padding: 8px 20px; font-size: 14px; min-width: 120px; }
|
||||||
|
|
||||||
/* 8. 彈出式輸出視窗 (Modal) 樣式 */
|
/* 8. 彈出式輸出視窗 (Modal) 樣式 */
|
||||||
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1000; align-items: center; justify-content: center; backdrop-filter: blur(3px); }
|
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1000; align-items: center; justify-content: center; -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); } /* 🌟 新增這行支援 Safari */
|
||||||
.modal-overlay.active { display: flex; animation: fadeIn 0.2s ease-out; }
|
.modal-overlay.active { display: flex; animation: fadeIn 0.2s ease-out; }
|
||||||
.modal-container { background: #1e1e1e; width: 95vw; max-width: 1800px; height: 85vh; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 15px 40px rgba(0,0,0,0.6); transform: translateY(20px); transition: transform 0.3s ease-out; }
|
.modal-container { background: #1e1e1e; width: 95vw; max-width: 1800px; height: 85vh; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 15px 40px rgba(0,0,0,0.6); transform: translateY(20px); transition: transform 0.3s ease-out; }
|
||||||
.modal-overlay.active .modal-container { transform: translateY(0); }
|
.modal-overlay.active .modal-container { transform: translateY(0); }
|
||||||
|
|
@ -168,6 +168,7 @@ button:hover { background-color: #3498db; }
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
|
-webkit-user-select: none; /* 🌟 新增這行支援 Safari */
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: #2c3e50; /* 保持原本的深色文字 */
|
color: #2c3e50; /* 保持原本的深色文字 */
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,11 @@ export function connectWebSocket(resetCallback) {
|
||||||
|
|
||||||
if (resetCallback) resetCallback(); // 呼叫 app.js 傳進來的重置函數
|
if (resetCallback) resetCallback(); // 呼叫 app.js 傳進來的重置函數
|
||||||
|
|
||||||
|
// 🌟 [修復] 切換設備時,徹底清空終端機舊有畫面與緩衝區
|
||||||
|
if (term) {
|
||||||
|
term.clear();
|
||||||
|
}
|
||||||
|
|
||||||
term.writeln(`\x1b[33mConnecting to ${connInfo.host} via WebSocket...\x1b[0m`);
|
term.writeln(`\x1b[33mConnecting to ${connInfo.host} via WebSocket...\x1b[0m`);
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const wsUrl = `${protocol}//${window.location.host}/ws/terminal?host=${encodeURIComponent(connInfo.host)}&username=${encodeURIComponent(connInfo.user)}&password=${encodeURIComponent(connInfo.pass)}`;
|
const wsUrl = `${protocol}//${window.location.host}/ws/terminal?host=${encodeURIComponent(connInfo.host)}&username=${encodeURIComponent(connInfo.user)}&password=${encodeURIComponent(connInfo.pass)}`;
|
||||||
|
|
|
||||||
|
|
@ -475,7 +475,11 @@ window.forceRenderFolderHTML = function(elementId) {
|
||||||
const contentDiv = document.getElementById(`content-${elementId}`);
|
const contentDiv = document.getElementById(`content-${elementId}`);
|
||||||
const cache = folderDataCache[elementId];
|
const cache = folderDataCache[elementId];
|
||||||
|
|
||||||
if (detailsEl && contentDiv && cache && detailsEl.dataset.loaded !== 'true') {
|
// 🌟 關鍵修復:移除 detailsEl.dataset.loaded !== 'true' 的限制!
|
||||||
|
// 因為如果使用者先手動展開了父資料夾,再點擊編輯,
|
||||||
|
// 原本的邏輯會因為 loaded === true 而拒絕往下遞迴渲染子資料夾,
|
||||||
|
// 導致子資料夾內的項目沒有被轉成輸入框。
|
||||||
|
if (detailsEl && contentDiv && cache) {
|
||||||
const currentPath = detailsEl.dataset.path;
|
const currentPath = detailsEl.dataset.path;
|
||||||
const isCommandGroup = detailsEl.dataset.isGroup === 'true';
|
const isCommandGroup = detailsEl.dataset.isGroup === 'true';
|
||||||
const mode = detailsEl.dataset.mode;
|
const mode = detailsEl.dataset.mode;
|
||||||
|
|
|
||||||
2444
target_code.txt
2444
target_code.txt
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue