feat: 1.工具維護者 (System Admin) 機制設計, 新增 URL 隱藏密鑰與彩蛋解鎖法。 2.使用者介面微調整。

This commit is contained in:
swpa 2026-05-14 15:42:18 +08:00
parent 3fea4a972a
commit de48fb1790
5 changed files with 97 additions and 41 deletions

View File

@ -13,7 +13,8 @@
</head> </head>
<body> <body>
<h1>🎙️ Harmonic CMTS Admin</h1> <!-- 1. 標題加上 id="app-title" -->
<h1 id="app-title" style="cursor: default;">🎙️ Harmonic CMTS Admin</h1>
<div class="global-settings"> <div class="global-settings">
<label><strong>🌐 目標設備:</strong></label> <label><strong>🌐 目標設備:</strong></label>
@ -157,7 +158,7 @@
<div id="config-tab" class="tab-content" style="overflow-y: auto; background-color: #fdfefe;"> <div id="config-tab" class="tab-content" style="overflow-y: auto; background-color: #fdfefe;">
<div class="manager-container"> <div class="manager-container">
<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: 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" 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-bonding-config">⚠️ MAC Domain 狀態感知配置精靈</option> <option value="form-bonding-config">⚠️ MAC Domain 狀態感知配置精靈</option>
@ -250,18 +251,18 @@
<!-- 表單 4完整設備配置樹狀圖 --> <!-- 表單 4完整設備配置樹狀圖 -->
<div id="form-full-config" class="task-form" style="display: none;"> <div id="form-full-config" class="task-form" style="display: none;">
<h3 style="display: flex; align-items: center; margin-bottom: 15px;"> <h3 style="display: flex; align-items: center; margin-top: 0; margin-bottom: 10px; font-size: 16px; color: #2c3e50;">
🌳 完整設備配置樹狀圖 🌳 完整設備配置樹狀圖
<!-- 🌟 一鍵掃描按鈕 --> <!-- 🌟 掃描按鈕 -->
<button id="global-scan-btn" onclick="scanAllMissingOptions()" disabled <button id="global-scan-btn" onclick="scanAllMissingOptions()" disabled
style="padding: 6px 12px; background-color: #8e44ad; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; margin-left: 20px; transition: all 0.3s; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> style="display: none; padding: 6px 12px; background-color: #8e44ad; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; margin-left: 20px; transition: all 0.3s; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
🔍 一鍵掃描缺失選項 🔍 掃描畫面缺失選項
</button> </button>
<!-- 🌟 請在這裡加入這段全新的按鈕 --> <!-- 🌟 請在這裡加入這段全新的按鈕 -->
<button id="clearCacheBtn" onclick="clearVisibleCache()" <button id="clearCacheBtn" onclick="clearVisibleCache()"
style="padding: 6px 12px; background-color: #f39c12; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; margin-left: 10px; transition: all 0.3s; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> style="display: none; padding: 6px 12px; background-color: #f39c12; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; margin-left: 10px; transition: all 0.3s; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
🔄 清除畫面選項快取 🔄 清除畫面選項快取
</button> </button>

View File

@ -354,7 +354,7 @@ async function scanAllMissingOptions() {
btn.disabled = false; btn.disabled = false;
btn.style.backgroundColor = "#8e44ad"; btn.style.backgroundColor = "#8e44ad";
btn.style.cursor = "pointer"; btn.style.cursor = "pointer";
btn.innerText = "🔍 一鍵掃描缺失選項"; btn.innerText = "🔍 掃描畫面缺失選項";
} }
localStorage.removeItem('scanLockUntil'); localStorage.removeItem('scanLockUntil');
setTimeout(() => statusEl.textContent = "", 5000); setTimeout(() => statusEl.textContent = "", 5000);
@ -368,7 +368,7 @@ async function scanAllMissingOptions() {
btn.disabled = false; btn.disabled = false;
btn.style.backgroundColor = "#8e44ad"; btn.style.backgroundColor = "#8e44ad";
btn.style.cursor = "pointer"; btn.style.cursor = "pointer";
btn.innerText = "🔍 一鍵掃描缺失選項"; btn.innerText = "🔍 掃描畫面缺失選項";
} }
localStorage.removeItem('scanLockUntil'); localStorage.removeItem('scanLockUntil');
} }
@ -398,11 +398,11 @@ async function clearVisibleCache() {
}); });
if (pathsToClear.length === 0) return alert("畫面上沒有展開的選項可以清除!\n請先展開您想重抓的資料夾。"); if (pathsToClear.length === 0) return alert("畫面上沒有展開的選項可以清除!\n請先展開您想重抓的資料夾。");
if (!confirm(`確定要清除畫面上 ${pathsToClear.length} 個選項的快取嗎?\n(清除後請重新點擊「一鍵掃描」)`)) return; if (!confirm(`確定要清除畫面上 ${pathsToClear.length} 個選項的快取嗎?\n(清除後請重新點擊「掃描畫面缺失選項」)`)) return;
try { try {
const result = await apiClearCache(pathsToClear); const result = await apiClearCache(pathsToClear);
alert(`✅ 成功清除 ${result.cleared_count} 筆快取!\n現在請點擊旁邊的「一鍵掃描缺失選項」來重新擷取。`); alert(`✅ 成功清除 ${result.cleared_count} 筆快取!\n現在請點擊旁邊的「掃描畫面缺失選項」來重新擷取。`);
} catch (error) { } catch (error) {
console.error("清除快取失敗:", error); console.error("清除快取失敗:", error);
alert("❌ 清除快取失敗,請檢查網路連線或後端 API 是否正確啟動。"); alert("❌ 清除快取失敗,請檢查網路連線或後端 API 是否正確啟動。");
@ -523,6 +523,48 @@ function closeModal(event) {
// 網頁載入時,檢查是否還有背景任務在跑 (維持掃描按鈕狀態) // 網頁載入時,檢查是否還有背景任務在跑 (維持掃描按鈕狀態)
document.addEventListener("DOMContentLoaded", checkScanButtonState); document.addEventListener("DOMContentLoaded", checkScanButtonState);
// ==========================================
// 🛡️ 系統管理員雙重解鎖邏輯 (URL + 彩蛋)
// ==========================================
document.addEventListener('DOMContentLoaded', () => {
const scanBtn = document.getElementById('global-scan-btn');
const clearBtn = document.getElementById('clearCacheBtn');
const appTitle = document.getElementById('app-title');
// 定義解鎖動作:把按鈕顯示出來
function unlockAdminFeatures() {
if (scanBtn) scanBtn.style.display = 'inline-block';
if (clearBtn) clearBtn.style.display = 'inline-block';
}
// 方案一URL 隱藏密鑰檢查 (?mode=godmode)
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('mode') === 'godmode') {
unlockAdminFeatures();
}
// 方案二:彩蛋解鎖 (對標題連點 5 次)
let clickCount = 0;
let clickTimer = null;
if (appTitle) {
appTitle.addEventListener('click', () => {
clickCount++;
if (clickCount === 5) {
unlockAdminFeatures();
alert('🔓 已解鎖系統維護者模式!');
clickCount = 0;
}
clearTimeout(clickTimer);
clickTimer = setTimeout(() => {
clickCount = 0;
}, 1000);
});
}
});
function checkScanButtonState() { function checkScanButtonState() {
const btn = document.getElementById('global-scan-btn'); const btn = document.getElementById('global-scan-btn');
if (!btn) return; if (!btn) return;

View File

@ -1,46 +1,47 @@
/* --- static/style.css --- */ /* --- static/style.css --- */
/* 1. 基礎滿版佈局 */ /* 1. 基礎滿版佈局 (全域 80% 縮放感) */
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; box-sizing: border-box; background-color: #f5f7fa; height: 100vh; display: flex; flex-direction: column; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 15px 20px; box-sizing: border-box; background-color: #f5f7fa; height: 100vh; display: flex; flex-direction: column; font-size: 14px; }
h1 { color: #2c3e50; margin-top: 0; flex-shrink: 0; margin-bottom: 15px; } h1 { color: #2c3e50; margin-top: 0; flex-shrink: 0; margin-bottom: 12px; font-size: 24px; }
/* 2. 全域設備設定區塊 */ /* 2. 全域設備設定區塊 */
.global-settings { background-color: #e8ecef; padding: 12px 20px; border-radius: 6px; border: 1px solid #d1d8dd; margin-bottom: 15px; display: flex; gap: 10px; align-items: center; flex-shrink: 0; flex-wrap: wrap; } .global-settings { background-color: #e8ecef; padding: 8px 15px; border-radius: 6px; border: 1px solid #d1d8dd; margin-bottom: 12px; display: flex; gap: 10px; align-items: center; flex-shrink: 0; flex-wrap: wrap; font-size: 13px; }
/* 3. 頁籤樣式 */ /* 3. 頁籤樣式 (🌟 修復 Hover 狀態,確保字體清晰) */
.tabs { display: flex; margin-bottom: 15px; border-bottom: 2px solid #ddd; flex-shrink: 0; } .tabs { display: flex; margin-bottom: 12px; border-bottom: 2px solid #ddd; flex-shrink: 0; }
.tab-btn { padding: 10px 20px; cursor: pointer; background: none; border: none; font-size: 16px; font-weight: bold; color: #7f8c8d; } .tab-btn { padding: 8px 18px; cursor: pointer; background: transparent; border: none; font-size: 15px; font-weight: bold; color: #7f8c8d; transition: all 0.2s ease; border-radius: 6px 6px 0 0; }
.tab-btn.active { color: #2980b9; border-bottom: 3px solid #2980b9; margin-bottom: -2px; } .tab-btn:hover { color: #2c3e50; background-color: #e2e8f0; } /* 滑鼠移入時:深色字+淺灰底 */
.tab-btn.active { color: #2980b9; border-bottom: 3px solid #2980b9; margin-bottom: -2px; background-color: transparent; }
/* 4. 內容區塊 */ /* 4. 內容區塊 */
.tab-content { display: none; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); flex-grow: 1; min-height: 0; } .tab-content { display: none; background: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); flex-grow: 1; min-height: 0; }
.tab-content.active { display: flex; flex-direction: column; } .tab-content.active { display: flex; flex-direction: column; }
/* 5. 控制列樣式 */ /* 5. 控制列樣式 (微縮元件) */
.control-group { margin-bottom: 15px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; flex-shrink: 0; } .control-group { margin-bottom: 12px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; flex-shrink: 0; }
select, input[type="text"], input[type="password"] { padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } select, input[type="text"], input[type="password"] { padding: 6px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; }
button { padding: 8px 16px; background-color: #2980b9; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: background-color 0.2s; } button { padding: 6px 14px; background-color: #2980b9; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 13px; transition: background-color 0.2s; }
button:hover { background-color: #3498db; } button:hover { background-color: #3498db; }
/* 6. 終端機與輸出區塊 */ /* 6. 終端機與輸出區塊 */
#terminal-container { flex-grow: 1; width: 100%; border-radius: 6px; background-color: #1e1e1e; box-shadow: inset 0 0 10px rgba(0,0,0,0.8); box-sizing: border-box; } #terminal-container { flex-grow: 1; width: 100%; border-radius: 6px; background-color: #1e1e1e; box-shadow: inset 0 0 10px rgba(0,0,0,0.8); box-sizing: border-box; }
.xterm { padding: 10px; } .xterm { padding: 10px; }
/* 7. 專業級表單排版 (全版面 + 靠左對齊) */ /* 7. 專業級表單排版 (🌟 縮小間距,解決空白過多) */
.manager-container { width: 100%; display: flex; flex-direction: column; gap: 20px; } .manager-container { width: 100%; display: flex; flex-direction: column; gap: 12px; }
.task-form { display: none; background: #ffffff; padding: 25px; border-radius: 8px; border: 1px solid #e2e8f0; box-shadow: 0 4px 6px rgba(0,0,0,0.02); } .task-form { display: none; background: #ffffff; padding: 18px 20px; border-radius: 8px; border: 1px solid #e2e8f0; box-shadow: 0 4px 6px rgba(0,0,0,0.02); }
.task-form.active { display: block; animation: fadeIn 0.3s ease-in-out; } .task-form.active { display: block; animation: fadeIn 0.3s ease-in-out; }
/* 網格系統:調整為更緊湊的高空間利用率排版 */ /* 網格系統:高空間利用率 */
.form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 15px; margin-bottom: 20px; justify-content: start; } .form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-bottom: 15px; justify-content: start; }
.form-row { display: flex; flex-direction: column; gap: 6px; } .form-row { display: flex; flex-direction: column; gap: 4px; }
.form-row label { font-size: 13px; font-weight: bold; color: #34495e; } .form-row label { font-size: 12px; font-weight: bold; color: #34495e; }
.form-row input[type="text"], .form-row select { width: 100%; box-sizing: border-box; padding: 8px; background-color: #f8f9fa; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 14px; } .form-row input[type="text"], .form-row select { width: 100%; box-sizing: border-box; padding: 6px 8px; background-color: #f8f9fa; border: 1px solid #cbd5e1; border-radius: 4px; font-size: 13px; }
.form-row input[type="text"]:focus, .form-row select:focus { outline: none; border-color: #2980b9; background-color: #fff; } .form-row input[type="text"]:focus, .form-row select:focus { outline: none; border-color: #2980b9; background-color: #fff; }
/* 表單底部操作區 */ /* 表單底部操作區 */
.form-actions { display: flex; justify-content: flex-start; align-items: center; gap: 20px; padding-top: 15px; border-top: 1px solid #ecf0f1; } .form-actions { display: flex; justify-content: flex-start; align-items: center; gap: 15px; padding-top: 12px; border-top: 1px solid #ecf0f1; }
.form-actions button { padding: 10px 25px; font-size: 15px; min-width: 150px; } .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; backdrop-filter: blur(3px); }

View File

@ -9,13 +9,23 @@ export let isConnected = false;
// 3. 終端機文字上色 (內部使用,不需要 export) // 3. 終端機文字上色 (內部使用,不需要 export)
function colorizeTerminalStream(text) { function colorizeTerminalStream(text) {
if (text.includes('\x1b[')) return text;
let res = 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'); // 1. IP 地址:改為「柔和冰藍色」 (移除 1; 粗體)
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(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/g, '\x1b[38;5;111m$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'); // 2. MAC 地址:改為「柔和薰衣草紫」 (移除 1; 粗體)
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[38;5;140m$1\x1b[0m');
// 3. 正常狀態:維持「柔和薄荷綠」
res = res.replace(/\b([a-z]-online|online|init\([^)]+\)|up|active|success|yes)\b/gi, '\x1b[38;5;114m$1\x1b[0m');
// 4. 異常狀態:維持「柔和珊瑚紅」
res = res.replace(/\b(offline|down|admin-down|error|fail|failed|shutdown|reject|invalid|no)\b/gi, '\x1b[38;5;204m$1\x1b[0m');
// 5. 介面名稱 (Cable/RPD等):改為「柔和奶油黃」 (原本是 \x1b[33m)
res = res.replace(/\b(Cable|GigabitEthernet|TenGigabitEthernet|Upstream|Downstream|Mac-domain|bonding-group|rpd)\b/gi, '\x1b[38;5;179m$1\x1b[0m');
return res; return res;
} }

View File

@ -1,5 +1,7 @@
{ {
"hidden_keys": [ "hidden_keys": [
"ssh" "ssh",
"hostname",
"clock"
] ]
} }