Refactor: 使用 ES6 Modules 抽離 Terminal 邏輯
This commit is contained in:
parent
8f4faa8956
commit
7592643e3e
|
|
@ -374,7 +374,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 引入獨立的 JavaScript 邏輯 -->
|
||||
<script src="/static/app.js"></script>
|
||||
<script type="module" src="/static/app.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
153
static/app.js
153
static/app.js
|
|
@ -1,11 +1,18 @@
|
|||
/* --- static/app.js --- */
|
||||
|
||||
// 1. 從 terminal.js 引入我們需要的功能
|
||||
import { initTerminal, connectWebSocket, disconnectWebSocket, injectCommand, downloadTerminal } from './terminal.js';
|
||||
|
||||
// 2. 將這些功能綁定到 window 上,這樣 HTML 裡的 onclick 才找得到它們!
|
||||
window.connectWebSocket = () => connectWebSocket(resetAllManagerData);
|
||||
window.disconnectWebSocket = disconnectWebSocket;
|
||||
window.injectCommand = injectCommand;
|
||||
window.downloadTerminal = downloadTerminal;
|
||||
|
||||
// ==========================================
|
||||
// 1. 全域變數與初始化
|
||||
// ==========================================
|
||||
let term, fitAddon, ws;
|
||||
let currentMacDomainData = null;
|
||||
let isConnected = false;
|
||||
|
||||
// 💡 新增:追蹤當前編輯狀態
|
||||
let currentEditPath = null;
|
||||
|
|
@ -1145,148 +1152,6 @@ function resetAllManagerData() {
|
|||
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;
|
||||
|
||||
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 = () => {
|
||||
isConnected = true;
|
||||
const btnLoadTask = document.getElementById('btn-load-task');
|
||||
if (btnLoadTask) {
|
||||
btnLoadTask.disabled = false;
|
||||
btnLoadTask.style.backgroundColor = '#c0392b';
|
||||
btnLoadTask.style.cursor = 'pointer';
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
isConnected = false;
|
||||
const btnLoadTask = document.getElementById('btn-load-task');
|
||||
if (btnLoadTask) {
|
||||
btnLoadTask.disabled = true;
|
||||
btnLoadTask.style.backgroundColor = '#95a5a6';
|
||||
btnLoadTask.style.cursor = 'not-allowed';
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
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 呼叫
|
||||
// ==========================================
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
// --- static/terminal.js ---
|
||||
|
||||
// 1. 從剛剛建立的 utils.js 引入工具
|
||||
import { getGlobalConnectionInfo } from './utils.js';
|
||||
|
||||
// 2. 宣告終端機專用的變數
|
||||
let term, fitAddon, ws;
|
||||
export let isConnected = false;
|
||||
|
||||
// 3. 終端機文字上色 (內部使用,不需要 export)
|
||||
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;
|
||||
}
|
||||
|
||||
// 4. 匯出 (export) 所有需要被外部呼叫的函數
|
||||
export 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);
|
||||
});
|
||||
}
|
||||
|
||||
export function connectWebSocket(resetCallback) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) return;
|
||||
const connInfo = getGlobalConnectionInfo();
|
||||
if (!connInfo) return;
|
||||
|
||||
if (resetCallback) resetCallback(); // 呼叫 app.js 傳進來的重置函數
|
||||
|
||||
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 = () => {
|
||||
isConnected = true;
|
||||
const btnLoadTask = document.getElementById('btn-load-task');
|
||||
if (btnLoadTask) {
|
||||
btnLoadTask.disabled = false;
|
||||
btnLoadTask.style.backgroundColor = '#c0392b';
|
||||
btnLoadTask.style.cursor = 'pointer';
|
||||
}
|
||||
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 = () => {
|
||||
isConnected = false;
|
||||
const btnLoadTask = document.getElementById('btn-load-task');
|
||||
if (btnLoadTask) {
|
||||
btnLoadTask.disabled = true;
|
||||
btnLoadTask.style.backgroundColor = '#95a5a6';
|
||||
btnLoadTask.style.cursor = 'not-allowed';
|
||||
}
|
||||
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');
|
||||
|
||||
if (resetCallback) resetCallback();
|
||||
};
|
||||
}
|
||||
|
||||
export function disconnectWebSocket() { if (ws) ws.close(); }
|
||||
|
||||
export function injectCommand() {
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) return alert("請先點擊「連線至 CMTS」!");
|
||||
const cmd = document.getElementById('fixedCmd').value;
|
||||
ws.send(cmd + '\r');
|
||||
term.focus();
|
||||
}
|
||||
|
||||
export 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 '';
|
||||
}
|
||||
}
|
||||
|
||||
export 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// --- static/utils.js ---
|
||||
|
||||
// 加上 export,讓其他檔案可以使用這個函數
|
||||
export 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 };
|
||||
}
|
||||
Loading…
Reference in New Issue