607 lines
28 KiB
JavaScript
607 lines
28 KiB
JavaScript
/* --- static/app.js --- */
|
||
|
||
// ==========================================
|
||
// 1. 全域變數與初始化
|
||
// ==========================================
|
||
let term, fitAddon, ws;
|
||
let currentMacDomainData = null;
|
||
|
||
window.onload = initTerminal;
|
||
window.onresize = () => { if (fitAddon) fitAddon.fit(); };
|
||
|
||
// ==========================================
|
||
// 2. UI 互動與頁籤切換
|
||
// ==========================================
|
||
function switchTab(tabId, element) {
|
||
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
|
||
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
||
|
||
const targetTab = document.getElementById(tabId);
|
||
if (targetTab) targetTab.classList.add('active');
|
||
if (element) element.classList.add('active');
|
||
|
||
if(tabId === 'cli-tab' && fitAddon) setTimeout(() => fitAddon.fit(), 50);
|
||
|
||
// 💡 已經移除自動觸發 switchConfigTask(),避免切換頁籤時覆蓋使用者資料
|
||
}
|
||
|
||
// 💡 查詢頁籤專用的切換邏輯
|
||
function switchQueryTask() {
|
||
// 只隱藏 query-tab 裡面的表單
|
||
document.querySelectorAll('#query-tab .task-form').forEach(form => form.classList.remove('active'));
|
||
const selectedTaskId = document.getElementById('queryTask').value;
|
||
const targetForm = document.getElementById(selectedTaskId);
|
||
if (targetForm) targetForm.classList.add('active');
|
||
}
|
||
|
||
// 💡 配置頁籤專用的切換邏輯 (現在只負責切換表單顯示,不自動抓資料)
|
||
function switchConfigTask() {
|
||
// 只隱藏 config-tab 裡面的表單
|
||
document.querySelectorAll('#config-tab .task-form').forEach(form => form.classList.remove('active'));
|
||
const selectedTaskId = document.getElementById('configTask').value;
|
||
const targetForm = document.getElementById(selectedTaskId);
|
||
if (targetForm) targetForm.classList.add('active');
|
||
}
|
||
|
||
// 💡 新增的函數:由「🚀 載入任務」按鈕手動觸發
|
||
function startConfigTask() {
|
||
// 1. 先確保對應的表單有顯示出來
|
||
switchConfigTask();
|
||
|
||
// 2. 根據選擇的任務,決定要向設備抓取什麼資料
|
||
const selectedTaskId = document.getElementById('configTask').value;
|
||
|
||
if (selectedTaskId === 'form-bonding-config') {
|
||
// 如果使用者正在編輯到一半,給予警告提示
|
||
if (currentMacDomainData && !confirm("重新載入將會清除您目前未套用的設定,確定要繼續嗎?")) {
|
||
return; // 使用者取消,中斷執行
|
||
}
|
||
|
||
// 確定載入,重置畫面並向設備抓取 MAC Domain 清單
|
||
resetAllManagerData();
|
||
loadMacDomainList();
|
||
}
|
||
}
|
||
|
||
// 💡 新增函數:向後端請求 MAC Domain 清單並動態生成下拉選單
|
||
async function loadMacDomainList() {
|
||
const connInfo = getGlobalConnectionInfo();
|
||
if (!connInfo) return;
|
||
|
||
const mdSelect = document.getElementById('cfgMacDomain');
|
||
const statusSpan = document.getElementById('fetchStatus');
|
||
|
||
// 顯示載入中狀態
|
||
mdSelect.innerHTML = '<option value="">⏳ 查詢設備中...</option>';
|
||
statusSpan.textContent = "正在自動抓取現有 MAC Domain 清單...";
|
||
statusSpan.style.color = "#f39c12";
|
||
|
||
try {
|
||
const url = `/api/v1/cmts-mac-domain-list?host=${encodeURIComponent(connInfo.host)}&username=${encodeURIComponent(connInfo.user)}&password=${encodeURIComponent(connInfo.pass)}`;
|
||
const response = await fetch(url);
|
||
const result = await response.json();
|
||
|
||
if (result.status === 'success' && result.data.length > 0) {
|
||
mdSelect.innerHTML = ''; // 清空預設選項
|
||
|
||
// 將後端抓到的陣列一一塞入下拉選單
|
||
result.data.forEach(md => {
|
||
const option = document.createElement('option');
|
||
option.value = md;
|
||
option.textContent = md;
|
||
mdSelect.appendChild(option);
|
||
});
|
||
|
||
statusSpan.textContent = "✅ 清單抓取成功!請選擇目標並點擊讀取配置。";
|
||
statusSpan.style.color = "#27ae60";
|
||
} else {
|
||
mdSelect.innerHTML = '<option value="">❌ 找不到資料</option>';
|
||
statusSpan.textContent = "無法解析清單,請確認設備狀態。";
|
||
statusSpan.style.color = "#e74c3c";
|
||
}
|
||
} catch (error) {
|
||
mdSelect.innerHTML = '<option value="">❌ 連線錯誤</option>';
|
||
statusSpan.textContent = "連線失敗:" + error;
|
||
statusSpan.style.color = "#e74c3c";
|
||
}
|
||
}
|
||
|
||
function toggleQueryInputs(mode) {
|
||
const type = document.getElementById('queryType' + mode).value;
|
||
const targetInput = document.getElementById('queryTarget' + mode);
|
||
const globalTypes = ['partial_mode', 'hop', 'flap_list', 'flap_sum'];
|
||
|
||
if (globalTypes.includes(type)) {
|
||
targetInput.disabled = true;
|
||
targetInput.style.backgroundColor = '#e8ecef';
|
||
targetInput.placeholder = "此查詢為全域指令,不需輸入目標";
|
||
targetInput.value = "";
|
||
} else {
|
||
targetInput.disabled = false;
|
||
targetInput.style.backgroundColor = '#f8f9fa';
|
||
targetInput.placeholder = mode === 'Cm' ? "例: 6467.7240.4076 (留白則查詢全體)" : "例: 13:0 (留白則查詢全體)";
|
||
}
|
||
}
|
||
|
||
// 💡 新增函數:重置所有 Manager 頁籤的暫存資料與畫面
|
||
function resetAllManagerData() {
|
||
// 1. 清空記憶體中的設定資料
|
||
currentMacDomainData = null;
|
||
|
||
// 2. 隱藏 MAC Domain 配置表單與 CLI 預覽區塊
|
||
const configArea = document.getElementById('macDomainConfigArea');
|
||
if (configArea) configArea.style.display = 'none';
|
||
|
||
const previewEl = document.getElementById('cliPreviewContainer');
|
||
if (previewEl) {
|
||
previewEl.textContent = '';
|
||
previewEl.style.display = 'none';
|
||
}
|
||
|
||
// 3. 隱藏並禁用「正式套用設定」按鈕
|
||
const deployBtn = document.getElementById('btnDeployConfig');
|
||
if (deployBtn) {
|
||
deployBtn.style.display = 'none';
|
||
deployBtn.disabled = true;
|
||
}
|
||
|
||
// 4. 重置 MAC Domain 下拉選單與狀態文字
|
||
const mdSelect = document.getElementById('cfgMacDomain');
|
||
if (mdSelect) mdSelect.innerHTML = '<option value="">請先點擊上方載入任務...</option>';
|
||
|
||
const fetchStatus = document.getElementById('fetchStatus');
|
||
if (fetchStatus) {
|
||
fetchStatus.textContent = "請先讀取設備狀態...";
|
||
fetchStatus.style.color = "#7f8c8d";
|
||
}
|
||
|
||
// 5. (選擇性) 清空查詢表單的輸入框
|
||
document.getElementById('queryTargetCm').value = '';
|
||
document.getElementById('queryTargetRpd').value = '';
|
||
}
|
||
|
||
// ==========================================
|
||
// 3. 終端機 (Terminal) 與 WebSocket 邏輯
|
||
// ==========================================
|
||
function colorizeTerminalStream(text) {
|
||
if (text.includes('\x1b[')) return text;
|
||
let res = text;
|
||
res = res.replace(/\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/g, '\x1b[1;34m$1\x1b[0m');
|
||
res = res.replace(/\b([0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}|[0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})\b/g, '\x1b[1;35m$1\x1b[0m');
|
||
res = res.replace(/\b(online|w-online|p-online|init\([^)]+\)|up|active|success|yes)\b/gi, '\x1b[1;32m$1\x1b[0m');
|
||
res = res.replace(/\b(offline|down|admin-down|error|fail|failed|shutdown|reject|invalid|no)\b/gi, '\x1b[1;31m$1\x1b[0m');
|
||
res = res.replace(/\b(Cable|GigabitEthernet|TenGigabitEthernet|Upstream|Downstream|Mac-domain|bonding-group|rpd)\b/gi, '\x1b[33m$1\x1b[0m');
|
||
return res;
|
||
}
|
||
|
||
function initTerminal() {
|
||
term = new Terminal({ cursorBlink: true, theme: { background: '#1e1e1e', foreground: '#e0e0e0' }, fontFamily: 'Courier New, monospace', fontSize: 15, scrollback: 100000 });
|
||
fitAddon = new FitAddon.FitAddon();
|
||
term.loadAddon(fitAddon);
|
||
term.open(document.getElementById('terminal-container'));
|
||
fitAddon.fit();
|
||
term.writeln('Welcome to Harmonic cOS Web Terminal.');
|
||
term.writeln('Please confirm the target device above and click "連線至 CMTS"... \r\n');
|
||
|
||
term.onData((data) => {
|
||
if (ws && ws.readyState === WebSocket.OPEN) ws.send(data);
|
||
});
|
||
}
|
||
|
||
function getGlobalConnectionInfo() {
|
||
const host = document.getElementById('cmtsHost').value.trim();
|
||
const user = document.getElementById('cmtsUser').value.trim();
|
||
const pass = document.getElementById('cmtsPass').value.trim();
|
||
if (!host || !user || !pass) {
|
||
alert("請在畫面上方完整輸入目標設備的 IP、帳號與密碼!");
|
||
return null;
|
||
}
|
||
return { host, user, pass };
|
||
}
|
||
|
||
function connectWebSocket() {
|
||
if (ws && ws.readyState === WebSocket.OPEN) return;
|
||
const connInfo = getGlobalConnectionInfo();
|
||
if (!connInfo) return;
|
||
|
||
// 💡 關鍵修改 1:在發起新連線前,強制清空舊設備的所有殘留資料
|
||
resetAllManagerData();
|
||
|
||
term.writeln(`\x1b[33mConnecting to ${connInfo.host} via WebSocket...\x1b[0m`);
|
||
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)}`;
|
||
|
||
ws = new WebSocket(wsUrl);
|
||
|
||
ws.onopen = () => {
|
||
document.getElementById('wsStatus').textContent = `狀態:已連線`;
|
||
document.getElementById('wsStatus').style.color = '#27ae60';
|
||
document.getElementById('btnConnect').style.display = 'none';
|
||
document.getElementById('btnDisconnect').style.display = 'inline-block';
|
||
document.getElementById('cmtsHost').disabled = true;
|
||
document.getElementById('cmtsUser').disabled = true;
|
||
document.getElementById('cmtsPass').disabled = true;
|
||
term.focus();
|
||
};
|
||
|
||
ws.onmessage = (event) => {
|
||
term.write(colorizeTerminalStream(event.data));
|
||
};
|
||
|
||
ws.onclose = () => {
|
||
document.getElementById('wsStatus').textContent = '狀態:已斷線';
|
||
document.getElementById('wsStatus').style.color = '#c0392b';
|
||
document.getElementById('btnConnect').style.display = 'inline-block';
|
||
document.getElementById('btnDisconnect').style.display = 'none';
|
||
document.getElementById('cmtsHost').disabled = false;
|
||
document.getElementById('cmtsUser').disabled = false;
|
||
document.getElementById('cmtsPass').disabled = false;
|
||
term.writeln('\r\n\x1b[31m--- Connection Closed ---\x1b[0m\r\n');
|
||
|
||
// 💡 關鍵修改 2:斷線時也自動重置畫面
|
||
resetAllManagerData();
|
||
};
|
||
}
|
||
|
||
function disconnectWebSocket() { if (ws) ws.close(); }
|
||
|
||
function injectCommand() {
|
||
if (!ws || ws.readyState !== WebSocket.OPEN) return alert("請先點擊「連線至 CMTS」!");
|
||
const cmd = document.getElementById('fixedCmd').value;
|
||
ws.send(cmd + '\r');
|
||
term.focus();
|
||
}
|
||
|
||
function getTerminalText() {
|
||
if (!term) return '';
|
||
try {
|
||
if (term.hasSelection()) return term.getSelection();
|
||
const buffer = term.buffer.active;
|
||
let text = '';
|
||
for (let i = 0; i < buffer.length; i++) {
|
||
const line = buffer.getLine(i);
|
||
if (line) text += line.translateToString(true) + '\n';
|
||
}
|
||
return text;
|
||
} catch (err) {
|
||
console.error("讀取終端機文字失敗:", err);
|
||
return '';
|
||
}
|
||
}
|
||
|
||
function downloadTerminal() {
|
||
const text = getTerminalText();
|
||
if (!text.trim()) return alert("沒有內容可下載!");
|
||
try {
|
||
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
|
||
a.download = `cmts_terminal_log_${timestamp}.txt`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
} catch (err) {
|
||
console.error("下載失敗:", err);
|
||
}
|
||
}
|
||
|
||
// ==========================================
|
||
// 4. 綜合查詢 API 呼叫
|
||
// ==========================================
|
||
async function executeQuery(mode) {
|
||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||
alert("請先點擊畫面上方的「連線至 CMTS」按鈕!");
|
||
return;
|
||
}
|
||
|
||
const connInfo = getGlobalConnectionInfo();
|
||
if (!connInfo) return;
|
||
|
||
const queryType = document.getElementById('queryType' + mode).value;
|
||
const target = document.getElementById('queryTarget' + mode).value.trim();
|
||
const isDebug = document.getElementById('debugQuery' + mode).checked;
|
||
|
||
openModal(connInfo.host);
|
||
const outputEl = document.getElementById('modalOutput');
|
||
outputEl.innerHTML = `<span style="color: #f39c12;">正在向 ${connInfo.host} 查詢中,請稍候...</span>`;
|
||
|
||
try {
|
||
const url = `/api/v1/cmts-query?query_type=${queryType}&target=${encodeURIComponent(target)}&host=${encodeURIComponent(connInfo.host)}&username=${encodeURIComponent(connInfo.user)}&password=${encodeURIComponent(connInfo.pass)}`;
|
||
const response = await fetch(url);
|
||
const result = await response.json();
|
||
|
||
if (result.status === 'success') {
|
||
outputEl.style.color = "#e0e0e0";
|
||
if (isDebug) {
|
||
outputEl.textContent = `[DEBUG] 實際發送指令: ${result.command}\n==================================================\n${result.data}`;
|
||
} else {
|
||
outputEl.textContent = result.data;
|
||
}
|
||
} else {
|
||
outputEl.style.color = "#e74c3c";
|
||
outputEl.textContent = `查詢失敗:\n${result.detail || result.message}`;
|
||
}
|
||
} catch (error) {
|
||
outputEl.style.color = "#e74c3c";
|
||
outputEl.textContent = `連線失敗: ${error}`;
|
||
}
|
||
}
|
||
|
||
// ==========================================
|
||
// 5. MAC Domain 配置精靈邏輯
|
||
// ==========================================
|
||
async function fetchMacDomainConfig() {
|
||
const target = document.getElementById('cfgMacDomain').value.trim();
|
||
const connInfo = getGlobalConnectionInfo();
|
||
if (!connInfo || !target) return alert("請確認設備連線資訊與 MAC Domain!");
|
||
|
||
document.getElementById('fetchStatus').textContent = "⏳ 正在讀取並解析設定中...";
|
||
document.getElementById('fetchStatus').style.color = "#f39c12";
|
||
|
||
try {
|
||
const url = `/api/v1/cmts-mac-domain-config?target=${encodeURIComponent(target)}&host=${encodeURIComponent(connInfo.host)}&username=${encodeURIComponent(connInfo.user)}&password=${encodeURIComponent(connInfo.pass)}`;
|
||
const response = await fetch(url);
|
||
const result = await response.json();
|
||
|
||
if (result.status === 'success') {
|
||
currentMacDomainData = result.data;
|
||
populateFormFromData();
|
||
document.getElementById('macDomainConfigArea').style.display = 'block';
|
||
document.getElementById('fetchStatus').textContent = "✅ 讀取成功!";
|
||
document.getElementById('fetchStatus').style.color = "#27ae60";
|
||
} else {
|
||
alert("讀取失敗:" + result.detail);
|
||
document.getElementById('fetchStatus').textContent = "❌ 讀取失敗";
|
||
}
|
||
} catch (error) {
|
||
alert("連線錯誤:" + error);
|
||
}
|
||
}
|
||
|
||
function populateFormFromData() {
|
||
const d = currentMacDomainData;
|
||
|
||
// 💡 正名工程:對齊後端 common_settings
|
||
document.getElementById('g_ip_prov').value = d.common_settings['ip-provisioning-mode'] || 'dual-stack';
|
||
document.getElementById('g_diplexer').value = d.common_settings['diplexer-band-edge'] || 'enabled';
|
||
document.getElementById('g_bat31').value = d.common_settings['cm-battery-mode-31-support'] || 'disabled';
|
||
document.getElementById('g_bat30').value = d.common_settings['cm-battery-mode-30-support'] || 'disabled';
|
||
document.getElementById('g_docsis40').value = d.common_settings['docsis40'] || 'disabled';
|
||
document.getElementById('g_ds_dyn').value = d.common_settings['ds-dynamic-bonding-group'] || 'disabled';
|
||
document.getElementById('g_us_dyn').value = d.common_settings['us-dynamic-bonding-group'] || 'disabled';
|
||
|
||
// 💡 正名工程:對齊後端 basic_channel_sets
|
||
document.getElementById('b_admin').value = d.basic_channel_sets['admin-state'] || 'down';
|
||
document.getElementById('b_ds_pri').value = d.basic_channel_sets['ds-primary-set'] || '';
|
||
document.getElementById('b_ds_non_pri').value = d.basic_channel_sets['ds-non-primary-set'] || '';
|
||
document.getElementById('b_us_phy').value = d.basic_channel_sets['us-phy-channel-set'] || '';
|
||
document.getElementById('b_ds_ofdm').value = d.basic_channel_sets['ds-ofdm-set'] || '';
|
||
document.getElementById('b_us_ofdma').value = d.basic_channel_sets['us-ofdma-set'] || '';
|
||
|
||
// 💡 正名工程:對齊後端 static_ds_bonding_groups
|
||
const dsSelect = document.getElementById('select_ds_group');
|
||
dsSelect.innerHTML = '<option value="_new_">[➕ 新增 DS Group]</option>';
|
||
Object.keys(d.static_ds_bonding_groups).forEach(grp => dsSelect.innerHTML += `<option value="${grp}">${grp}</option>`);
|
||
if (Object.keys(d.static_ds_bonding_groups).length > 0) dsSelect.value = Object.keys(d.static_ds_bonding_groups)[0];
|
||
|
||
// 💡 正名工程:對齊後端 static_us_bonding_groups
|
||
const usSelect = document.getElementById('select_us_group');
|
||
usSelect.innerHTML = '<option value="_new_">[➕ 新增 US Group]</option>';
|
||
Object.keys(d.static_us_bonding_groups).forEach(grp => usSelect.innerHTML += `<option value="${grp}">${grp}</option>`);
|
||
if (Object.keys(d.static_us_bonding_groups).length > 0) usSelect.value = Object.keys(d.static_us_bonding_groups)[0];
|
||
|
||
toggleGroupVisibility();
|
||
loadDsGroupData();
|
||
loadUsGroupData();
|
||
}
|
||
|
||
// 💡 新增函數:放棄修改,還原回記憶體中的原始設備設定
|
||
function revertFormChanges() {
|
||
// 防呆:確保目前記憶體中有原始資料可以還原
|
||
if (!currentMacDomainData) return;
|
||
|
||
// 加上確認對話框,避免使用者誤觸
|
||
if (!confirm("確定要放棄目前的修改,還原回設備原始的設定值嗎?")) {
|
||
return;
|
||
}
|
||
|
||
// 1. 重新利用記憶體中的原始 JSON 資料填入表單
|
||
populateFormFromData();
|
||
|
||
// 2. 隱藏已經產生的 CLI 預覽與套用按鈕 (因為狀態已經改變)
|
||
const previewEl = document.getElementById('cliPreviewContainer');
|
||
if (previewEl) {
|
||
previewEl.textContent = '';
|
||
previewEl.style.display = 'none';
|
||
}
|
||
|
||
const deployBtn = document.getElementById('btnDeployConfig');
|
||
if (deployBtn) {
|
||
deployBtn.style.display = 'none';
|
||
deployBtn.disabled = true;
|
||
}
|
||
|
||
// 3. 給予使用者視覺回饋
|
||
const fetchStatus = document.getElementById('fetchStatus');
|
||
fetchStatus.textContent = "🔄 已還原為原始設定!";
|
||
fetchStatus.style.color = "#2980b9";
|
||
|
||
// 3秒後把狀態文字改回綠色的讀取成功
|
||
setTimeout(() => {
|
||
fetchStatus.textContent = "✅ 讀取成功!";
|
||
fetchStatus.style.color = "#27ae60";
|
||
}, 3000);
|
||
}
|
||
|
||
function toggleGroupVisibility() {
|
||
const dsDyn = document.getElementById('g_ds_dyn').value;
|
||
const usDyn = document.getElementById('g_us_dyn').value;
|
||
document.getElementById('section_group_ds').style.display = (dsDyn === 'disabled') ? 'block' : 'none';
|
||
document.getElementById('section_group_us').style.display = (usDyn === 'disabled') ? 'block' : 'none';
|
||
}
|
||
|
||
function loadDsGroupData() {
|
||
const grp = document.getElementById('select_ds_group').value;
|
||
const newGrpInput = document.getElementById('input_new_ds_group');
|
||
if (grp === '_new_') {
|
||
newGrpInput.style.display = 'inline-block';
|
||
document.getElementById('ds_g_admin').value = 'down';
|
||
document.getElementById('ds_g_down').value = '';
|
||
document.getElementById('ds_g_ofdm').value = '';
|
||
document.getElementById('ds_g_fdx').value = '';
|
||
} else {
|
||
newGrpInput.style.display = 'none';
|
||
// 💡 正名工程:對齊後端 static_ds_bonding_groups
|
||
const data = currentMacDomainData.static_ds_bonding_groups[grp];
|
||
document.getElementById('ds_g_admin').value = data['admin-state'] || 'down';
|
||
document.getElementById('ds_g_down').value = data['down-channel-set'] || '';
|
||
document.getElementById('ds_g_ofdm').value = data['ofdm-channel-set'] || '';
|
||
document.getElementById('ds_g_fdx').value = data['fdx-ofdm-channel-set'] || '';
|
||
}
|
||
}
|
||
|
||
function loadUsGroupData() {
|
||
const grp = document.getElementById('select_us_group').value;
|
||
const newGrpInput = document.getElementById('input_new_us_group');
|
||
if (grp === '_new_') {
|
||
newGrpInput.style.display = 'inline-block';
|
||
document.getElementById('us_g_admin').value = 'down';
|
||
document.getElementById('us_g_us').value = '';
|
||
document.getElementById('us_g_ofdma').value = '';
|
||
document.getElementById('us_g_fdx').value = '';
|
||
} else {
|
||
newGrpInput.style.display = 'none';
|
||
// 💡 正名工程:對齊後端 static_us_bonding_groups
|
||
const data = currentMacDomainData.static_us_bonding_groups[grp];
|
||
document.getElementById('us_g_admin').value = data['admin-state'] || 'down';
|
||
document.getElementById('us_g_us').value = data['us-channel-set'] || '';
|
||
document.getElementById('us_g_ofdma').value = data['ofdma-channel-set'] || '';
|
||
document.getElementById('us_g_fdx').value = data['fdx-ofdma-channel-set'] || '';
|
||
}
|
||
}
|
||
|
||
function generateMacDomainCLI() {
|
||
const md = document.getElementById('cfgMacDomain').value.trim();
|
||
let cli = `! --- Harmonic MAC Domain Configuration SOP ---\n`;
|
||
|
||
// --- 區塊 1: MAC Domain Base ---
|
||
cli += `! 1. [區塊] MAC Domain 全域與 Base 設定\n`;
|
||
cli += `cable mac-domain ${md} admin-state down\ncommit\n`;
|
||
|
||
const baseCmds = [
|
||
`ip-provisioning-mode ${document.getElementById('g_ip_prov').value}`,
|
||
`diplexer-band-edge control ${document.getElementById('g_diplexer').value}`,
|
||
`cm-battery-mode-31-support ${document.getElementById('g_bat31').value}`,
|
||
`cm-battery-mode-30-support ${document.getElementById('g_bat30').value}`,
|
||
`docsis40 ${document.getElementById('g_docsis40').value}`,
|
||
`ds-dynamic-bonding-group ${document.getElementById('g_ds_dyn').value}`,
|
||
`us-dynamic-bonding-group ${document.getElementById('g_us_dyn').value}`,
|
||
`ds-primary-set ${document.getElementById('b_ds_pri').value}`,
|
||
`ds-non-primary-set ${document.getElementById('b_ds_non_pri').value}`,
|
||
`us-phy-channel-set ${document.getElementById('b_us_phy').value}`,
|
||
`ds-ofdm-set ${document.getElementById('b_ds_ofdm').value}`,
|
||
`us-ofdma-set ${document.getElementById('b_us_ofdma').value}`
|
||
];
|
||
baseCmds.forEach(cmd => {
|
||
if (!cmd.endsWith(" ")) cli += `cable mac-domain ${md} ${cmd}\n`;
|
||
});
|
||
cli += `cable mac-domain ${md} admin-state ${document.getElementById('b_admin').value}\ncommit\n`;
|
||
|
||
// --- 區塊 2: DS Bonding Group ---
|
||
if (document.getElementById('g_ds_dyn').value === 'disabled') {
|
||
const isNewDs = document.getElementById('select_ds_group').value === '_new_';
|
||
let dsName = isNewDs ? document.getElementById('input_new_ds_group').value.trim() : document.getElementById('select_ds_group').value;
|
||
|
||
if (dsName) {
|
||
cli += `\n! 2. [區塊] DS Bonding Group [${dsName}] 設定\n`;
|
||
if (!isNewDs) cli += `cable mac-domain ${md} ds-bonding-group ${dsName} admin-state down\ncommit\n`;
|
||
|
||
if (document.getElementById('ds_g_down').value) cli += `cable mac-domain ${md} ds-bonding-group ${dsName} down-channel-set ${document.getElementById('ds_g_down').value}\n`;
|
||
if (document.getElementById('ds_g_ofdm').value) cli += `cable mac-domain ${md} ds-bonding-group ${dsName} ofdm-channel-set ${document.getElementById('ds_g_ofdm').value}\n`;
|
||
if (document.getElementById('ds_g_fdx').value) cli += `cable mac-domain ${md} ds-bonding-group ${dsName} fdx-ofdm-channel-set ${document.getElementById('ds_g_fdx').value}\n`;
|
||
|
||
cli += `cable mac-domain ${md} ds-bonding-group ${dsName} admin-state ${document.getElementById('ds_g_admin').value}\ncommit\n`;
|
||
}
|
||
}
|
||
|
||
// --- 區塊 3: US Bonding Group ---
|
||
if (document.getElementById('g_us_dyn').value === 'disabled') {
|
||
const isNewUs = document.getElementById('select_us_group').value === '_new_';
|
||
let usName = isNewUs ? document.getElementById('input_new_us_group').value.trim() : document.getElementById('select_us_group').value;
|
||
|
||
if (usName) {
|
||
cli += `\n! 3. [區塊] US Bonding Group [${usName}] 設定\n`;
|
||
if (!isNewUs) cli += `cable mac-domain ${md} us-bonding-group ${usName} admin-state down\ncommit\n`;
|
||
|
||
if (document.getElementById('us_g_us').value) cli += `cable mac-domain ${md} us-bonding-group ${usName} us-channel-set ${document.getElementById('us_g_us').value}\n`;
|
||
if (document.getElementById('us_g_ofdma').value) cli += `cable mac-domain ${md} us-bonding-group ${usName} ofdma-channel-set ${document.getElementById('us_g_ofdma').value}\n`;
|
||
if (document.getElementById('us_g_fdx').value) cli += `cable mac-domain ${md} us-bonding-group ${usName} fdx-ofdma-channel-set ${document.getElementById('us_g_fdx').value}\n`;
|
||
|
||
cli += `cable mac-domain ${md} us-bonding-group ${usName} admin-state ${document.getElementById('us_g_admin').value}\ncommit\n`;
|
||
}
|
||
}
|
||
|
||
const previewEl = document.getElementById('cliPreviewContainer');
|
||
previewEl.textContent = cli;
|
||
previewEl.style.display = 'block';
|
||
|
||
const deployBtn = document.getElementById('btnDeployConfig');
|
||
deployBtn.style.display = 'inline-block';
|
||
deployBtn.disabled = false;
|
||
}
|
||
|
||
async function executeBondingConfig() {
|
||
if (!confirm("⚠️ 警告:此操作將導致 MAC Domain 重新啟動,影響用戶服務。確定要執行嗎?")) return;
|
||
|
||
const cliScript = document.getElementById('cliPreviewContainer').textContent;
|
||
const connInfo = getGlobalConnectionInfo();
|
||
if (!connInfo) return;
|
||
|
||
openModal(connInfo.host);
|
||
const outputEl = document.getElementById('modalOutput');
|
||
outputEl.innerHTML = `<span style="color: #f39c12;">🚀 正在排隊並執行配置腳本,這可能需要幾秒鐘,請勿關閉視窗...</span>\n\n${cliScript}`;
|
||
|
||
try {
|
||
const response = await fetch('/api/v1/cmts-config', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
script: cliScript,
|
||
host: connInfo.host,
|
||
username: connInfo.user,
|
||
password: connInfo.pass
|
||
})
|
||
});
|
||
const result = await response.json();
|
||
|
||
if (result.status === 'success') {
|
||
outputEl.style.color = "#e0e0e0";
|
||
outputEl.textContent = `✅ 設定執行完成!\n==================================================\n${result.data}`;
|
||
} else {
|
||
outputEl.style.color = "#e74c3c";
|
||
outputEl.textContent = `❌ 設定執行失敗:\n${result.message}`;
|
||
}
|
||
} catch (error) {
|
||
outputEl.style.color = "#e74c3c";
|
||
outputEl.textContent = `❌ 連線或伺服器錯誤: ${error}`;
|
||
}
|
||
}
|
||
|
||
// ==========================================
|
||
// 6. Modal 彈出視窗控制
|
||
// ==========================================
|
||
function openModal(targetInfo) {
|
||
document.getElementById('outputModal').classList.add('active');
|
||
document.getElementById('modalTargetInfo').textContent = targetInfo ? `(${targetInfo})` : '';
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function closeModal(event) {
|
||
if (event && event.target !== event.currentTarget && event.target.className !== 'modal-close') return;
|
||
document.getElementById('outputModal').classList.remove('active');
|
||
document.body.style.overflow = '';
|
||
}
|