Update app.py
Browse files
app.py
CHANGED
|
@@ -38,7 +38,7 @@ USER_DB = {
|
|
| 38 |
|
| 39 |
log_history = []
|
| 40 |
|
| 41 |
-
# ---
|
| 42 |
def check_anomaly(ip, action, log_time, overall_w, time_w, ip_w, dev_w):
|
| 43 |
if not AI_READY: return "success"
|
| 44 |
if action in ["login_attempt", "logout"]: return "success"
|
|
@@ -143,7 +143,6 @@ def record_log(ip, cookie, account, role, action, status, time_mode, custom_time
|
|
| 143 |
|
| 144 |
return pd.DataFrame(log_history)
|
| 145 |
|
| 146 |
-
# --- 3. 視角切換與登入控制邏輯 ---
|
| 147 |
def fast_login(target_account, ip, cookie, time_mode, custom_time, ow, tw, iw, dw):
|
| 148 |
role, name = USER_DB[target_account][1], USER_DB[target_account][2]
|
| 149 |
df = record_log(ip, cookie, target_account, role, "login_attempt", "success", time_mode, custom_time, ow, tw, iw, dw)
|
|
@@ -151,7 +150,6 @@ def fast_login(target_account, ip, cookie, time_mode, custom_time, ow, tw, iw, d
|
|
| 151 |
welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938 (⚡快速登入模式)"
|
| 152 |
target_tab = "student_tab" if role == "學生" else "admin_tab"
|
| 153 |
|
| 154 |
-
# 回傳切換畫面與選定的 Tab
|
| 155 |
return gr.update(visible=False), gr.update(visible=True), gr.update(selected=target_tab), "", welcome_msg, df, target_account
|
| 156 |
|
| 157 |
def process_login(ip, cookie, account, password, captcha, time_mode, custom_time, ow, tw, iw, dw):
|
|
@@ -172,31 +170,24 @@ def process_login(ip, cookie, account, password, captcha, time_mode, custom_time
|
|
| 172 |
if USER_DB[account][0] == password:
|
| 173 |
welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938"
|
| 174 |
df = record_log(ip, cookie, account, role, "login_attempt", "success", time_mode, custom_time, ow, tw, iw, dw)
|
| 175 |
-
|
| 176 |
target_tab = "student_tab" if role == "學生" else "admin_tab"
|
|
|
|
| 177 |
return gr.update(visible=False), gr.update(visible=True), gr.update(selected=target_tab), "", welcome_msg, df
|
| 178 |
|
| 179 |
df = record_log(ip, cookie, account, role, "login_attempt", "failed_password", time_mode, custom_time, ow, tw, iw, dw)
|
| 180 |
return fail_response("密碼錯誤!", df)
|
| 181 |
|
| 182 |
-
def toggle_role(
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
target_account = "student" if current_account == "admin" else "admin"
|
| 186 |
-
|
| 187 |
role, name = USER_DB[target_account][1], USER_DB[target_account][2]
|
| 188 |
df = record_log(ip, cookie, target_account, role, "login_attempt", "success", time_mode, custom_time, ow, tw, iw, dw)
|
| 189 |
|
| 190 |
-
welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938 (⚡
|
| 191 |
-
target_tab = "
|
| 192 |
|
| 193 |
return gr.update(selected=target_tab), welcome_msg, df, target_account
|
| 194 |
|
| 195 |
-
def logout(ip, cookie, account, time_mode, custom_time, ow, tw, iw, dw):
|
| 196 |
-
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 197 |
-
df = record_log(ip, cookie, account, role, "logout", "success", time_mode, custom_time, ow, tw, iw, dw)
|
| 198 |
-
return gr.update(visible=True), gr.update(visible=False), "", "", "", "", df
|
| 199 |
-
|
| 200 |
def simulate_click(ip, cookie, account, button_name, time_mode, custom_time, ow, tw, iw, dw):
|
| 201 |
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 202 |
return record_log(ip, cookie, account, role, button_name, "success", time_mode, custom_time, ow, tw, iw, dw)
|
|
@@ -205,6 +196,11 @@ def test_anomaly(ip, cookie, account, action_name, test_time_str, ow, tw, iw, dw
|
|
| 205 |
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 206 |
return record_log(ip, cookie, account, role, action_name, "success", "自訂時間 (模擬過去/未來)", test_time_str, ow, tw, iw, dw)
|
| 207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
def toggle_time_input(mode):
|
| 209 |
return gr.update(visible=(mode == "自訂時間 (模擬過去/未來)"))
|
| 210 |
|
|
@@ -215,7 +211,7 @@ custom_css = """
|
|
| 215 |
.ntut-title { color: #d9534f; font-size: 22px; font-weight: bold; text-align: center; margin-bottom: 20px;}
|
| 216 |
.weight-card { padding: 10px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-top: 10px; }
|
| 217 |
|
| 218 |
-
/* 🌟 新增:隱藏 Tabs 標題列的
|
| 219 |
.headless-tabs > div:first-child { display: none !important; }
|
| 220 |
.headless-tabs { border: none !important; background: transparent !important; }
|
| 221 |
"""
|
|
@@ -255,15 +251,15 @@ with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css)
|
|
| 255 |
welcome_text = gr.Markdown(value="", elem_classes="text-right")
|
| 256 |
logout_btn = gr.Button("登出", size="sm")
|
| 257 |
|
| 258 |
-
# 🌟 單一切換按鈕
|
| 259 |
with gr.Row():
|
| 260 |
-
btn_toggle_role = gr.Button("
|
|
|
|
| 261 |
gr.Markdown("---")
|
| 262 |
|
| 263 |
-
# 🌟
|
| 264 |
with gr.Tabs(selected="student_tab", elem_classes="headless-tabs") as role_tabs:
|
| 265 |
-
|
| 266 |
-
with gr.Tab("👨🎓 學生專區", id="student_tab"):
|
| 267 |
with gr.Accordion("▼ 1. 教務系統", open=True):
|
| 268 |
with gr.Row(): btn_eval = gr.Button("▶ 期末網路教學評量", size="sm"); btn_pre_select = gr.Button("▶ 期末網路預選系統", size="sm"); btn_add_drop = gr.Button("▶ 開學後加退選系統", size="sm")
|
| 269 |
with gr.Row(): btn_istudy = gr.Button("▶ 北科i學園PLUS", size="sm"); btn_card = gr.Button("▶ 學生證掛失及補發系統", size="sm"); btn_course = gr.Button("▶ 課程系統", size="sm")
|
|
@@ -275,7 +271,7 @@ with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css)
|
|
| 275 |
with gr.Accordion("▼ 惡意操作測試區", open=True):
|
| 276 |
btn_hack_score = gr.Button("💀 嘗試偷改成績 (SQL Injection)", variant="stop", size="sm")
|
| 277 |
|
| 278 |
-
with gr.Tab("
|
| 279 |
with gr.Accordion("▼ 防護強度微調 (AI 敏感度)", open=True, elem_classes="weight-card"):
|
| 280 |
gr.Markdown("<span style='color:gray; font-size:0.9em;'>手動輸入 0~100 的數值。提高強度會增加誤報率,但能更有效攔截新型攻擊。</span>")
|
| 281 |
with gr.Row():
|
|
@@ -290,33 +286,44 @@ with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css)
|
|
| 290 |
|
| 291 |
gr.Markdown("#### 1. 選課相關系統 (具備強烈季節性)")
|
| 292 |
with gr.Row():
|
| 293 |
-
t_pre_sel_norm = gr.Button("✅ 正常: 期末預選 (06-13 11:00)", size="sm")
|
|
|
|
| 294 |
with gr.Row():
|
| 295 |
-
t_course_norm = gr.Button("✅ 正常: 課程系統 (02-25 07:50)", size="sm")
|
|
|
|
| 296 |
with gr.Row():
|
| 297 |
-
t_add_drop_norm = gr.Button("✅ 正常: 開學加退選 (02-18 14:00)", size="sm")
|
|
|
|
| 298 |
|
| 299 |
gr.Markdown("#### 2. 期末與成績相關 (具備週期性)")
|
| 300 |
with gr.Row():
|
| 301 |
-
t_eval_norm = gr.Button("✅ 正常: 教學評量 (12-17 13:20)", size="sm")
|
|
|
|
| 302 |
with gr.Row():
|
| 303 |
-
t_score_norm = gr.Button("✅ 正常: 成績查詢 (11-17 18:50)", size="sm")
|
|
|
|
| 304 |
|
| 305 |
gr.Markdown("#### 3. 校務與行政服務 (生活與常規)")
|
| 306 |
with gr.Row():
|
| 307 |
-
t_dorm_norm = gr.Button("✅ 正常: 宿舍抽籤 (07-21 23:10)", size="sm")
|
|
|
|
| 308 |
with gr.Row():
|
| 309 |
-
t_schol_norm = gr.Button("✅ 正常: 獎學金申請 (03-12 14:00)", size="sm")
|
|
|
|
| 310 |
with gr.Row():
|
| 311 |
-
t_card_norm = gr.Button("✅ 正常: 學生證掛失 (10-23 13:40)", size="sm")
|
|
|
|
| 312 |
|
| 313 |
gr.Markdown("#### 4. 學習與日常輔助系統 (具備作息規律)")
|
| 314 |
with gr.Row():
|
| 315 |
-
t_istudy_norm = gr.Button("✅ 正常: i學園PLUS (10-18 16:00)", size="sm")
|
|
|
|
| 316 |
with gr.Row():
|
| 317 |
-
t_leave_norm = gr.Button("✅ 正常: 學生請假 (03-29 08:45)", size="sm")
|
|
|
|
| 318 |
with gr.Row():
|
| 319 |
-
t_vdesk_norm = gr.Button("✅ 正常: 軟體雲/WebMail (03-22 18:00)", size="sm")
|
|
|
|
| 320 |
|
| 321 |
# --- 綁定事件 ---
|
| 322 |
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
|
|
@@ -328,22 +335,22 @@ with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css)
|
|
| 328 |
inputs=[mock_ip, mock_cookie, acc_input, pwd_input, captcha_input, time_mode, custom_time_input] + weight_inputs,
|
| 329 |
outputs=[login_view, dashboard_view, role_tabs, login_msg, welcome_text, log_display]
|
| 330 |
)
|
| 331 |
-
|
| 332 |
logout_btn.click(
|
| 333 |
fn=logout,
|
| 334 |
inputs=[mock_ip, mock_cookie, acc_input, time_mode, custom_time_input] + weight_inputs,
|
| 335 |
-
outputs=[login_view, dashboard_view, acc_input, pwd_input, captcha_input, login_msg, log_display]
|
| 336 |
)
|
| 337 |
|
| 338 |
fast_inputs = [mock_ip, mock_cookie, time_mode, custom_time_input] + weight_inputs
|
| 339 |
-
|
| 340 |
-
btn_fast_student.click(fn=lambda ip, c, tm, ct, ow, tw, iw, dw: fast_login("student", ip, c, tm, ct, ow, tw, iw, dw), inputs=fast_inputs, outputs=[login_view, dashboard_view, role_tabs, login_msg, welcome_text, log_display, acc_input])
|
| 341 |
-
btn_fast_admin.click(fn=lambda ip, c, tm, ct, ow, tw, iw, dw: fast_login("admin", ip, c, tm, ct, ow, tw, iw, dw), inputs=fast_inputs, outputs=[login_view, dashboard_view, role_tabs, login_msg, welcome_text, log_display, acc_input])
|
| 342 |
|
| 343 |
-
|
|
|
|
|
|
|
|
|
|
| 344 |
btn_toggle_role.click(
|
| 345 |
fn=toggle_role,
|
| 346 |
-
inputs=[
|
| 347 |
outputs=[role_tabs, welcome_text, log_display, acc_input]
|
| 348 |
)
|
| 349 |
|
|
@@ -365,17 +372,31 @@ with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css)
|
|
| 365 |
)
|
| 366 |
|
| 367 |
test_mapping = [
|
| 368 |
-
(t_pre_sel_norm, "click_pre_select_system", "2026-06-13 11:00:00"),
|
| 369 |
-
(
|
| 370 |
-
(
|
| 371 |
-
(
|
| 372 |
-
(
|
| 373 |
-
(
|
| 374 |
-
|
| 375 |
-
(
|
| 376 |
-
(
|
| 377 |
-
(
|
| 378 |
-
(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
]
|
| 380 |
|
| 381 |
for btn, action_name, test_time_str in test_mapping:
|
|
|
|
| 38 |
|
| 39 |
log_history = []
|
| 40 |
|
| 41 |
+
# --- 3. 核心邏輯與真實 HDBSCAN 異常偵測 ---
|
| 42 |
def check_anomaly(ip, action, log_time, overall_w, time_w, ip_w, dev_w):
|
| 43 |
if not AI_READY: return "success"
|
| 44 |
if action in ["login_attempt", "logout"]: return "success"
|
|
|
|
| 143 |
|
| 144 |
return pd.DataFrame(log_history)
|
| 145 |
|
|
|
|
| 146 |
def fast_login(target_account, ip, cookie, time_mode, custom_time, ow, tw, iw, dw):
|
| 147 |
role, name = USER_DB[target_account][1], USER_DB[target_account][2]
|
| 148 |
df = record_log(ip, cookie, target_account, role, "login_attempt", "success", time_mode, custom_time, ow, tw, iw, dw)
|
|
|
|
| 150 |
welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938 (⚡快速登入模式)"
|
| 151 |
target_tab = "student_tab" if role == "學生" else "admin_tab"
|
| 152 |
|
|
|
|
| 153 |
return gr.update(visible=False), gr.update(visible=True), gr.update(selected=target_tab), "", welcome_msg, df, target_account
|
| 154 |
|
| 155 |
def process_login(ip, cookie, account, password, captcha, time_mode, custom_time, ow, tw, iw, dw):
|
|
|
|
| 170 |
if USER_DB[account][0] == password:
|
| 171 |
welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938"
|
| 172 |
df = record_log(ip, cookie, account, role, "login_attempt", "success", time_mode, custom_time, ow, tw, iw, dw)
|
|
|
|
| 173 |
target_tab = "student_tab" if role == "學生" else "admin_tab"
|
| 174 |
+
|
| 175 |
return gr.update(visible=False), gr.update(visible=True), gr.update(selected=target_tab), "", welcome_msg, df
|
| 176 |
|
| 177 |
df = record_log(ip, cookie, account, role, "login_attempt", "failed_password", time_mode, custom_time, ow, tw, iw, dw)
|
| 178 |
return fail_response("密碼錯誤!", df)
|
| 179 |
|
| 180 |
+
def toggle_role(current_tab, ip, cookie, time_mode, custom_time, ow, tw, iw, dw):
|
| 181 |
+
# 根據目前的 Tab 自動判定要切換成誰
|
| 182 |
+
target_account = "admin" if current_tab == "student_tab" else "student"
|
|
|
|
|
|
|
| 183 |
role, name = USER_DB[target_account][1], USER_DB[target_account][2]
|
| 184 |
df = record_log(ip, cookie, target_account, role, "login_attempt", "success", time_mode, custom_time, ow, tw, iw, dw)
|
| 185 |
|
| 186 |
+
welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938 (⚡快速切換模式)"
|
| 187 |
+
target_tab = "admin_tab" if current_tab == "student_tab" else "student_tab"
|
| 188 |
|
| 189 |
return gr.update(selected=target_tab), welcome_msg, df, target_account
|
| 190 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
def simulate_click(ip, cookie, account, button_name, time_mode, custom_time, ow, tw, iw, dw):
|
| 192 |
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 193 |
return record_log(ip, cookie, account, role, button_name, "success", time_mode, custom_time, ow, tw, iw, dw)
|
|
|
|
| 196 |
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 197 |
return record_log(ip, cookie, account, role, action_name, "success", "自訂時間 (模擬過去/未來)", test_time_str, ow, tw, iw, dw)
|
| 198 |
|
| 199 |
+
def logout(ip, cookie, account, time_mode, custom_time, ow, tw, iw, dw):
|
| 200 |
+
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 201 |
+
df = record_log(ip, cookie, account, role, "logout", "success", time_mode, custom_time, ow, tw, iw, dw)
|
| 202 |
+
return gr.update(visible=True), gr.update(visible=False), gr.update(), "", "", "", "", df
|
| 203 |
+
|
| 204 |
def toggle_time_input(mode):
|
| 205 |
return gr.update(visible=(mode == "自訂時間 (模擬過去/未來)"))
|
| 206 |
|
|
|
|
| 211 |
.ntut-title { color: #d9534f; font-size: 22px; font-weight: bold; text-align: center; margin-bottom: 20px;}
|
| 212 |
.weight-card { padding: 10px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-top: 10px; }
|
| 213 |
|
| 214 |
+
/* 🌟 新增:隱藏 Tabs 標題列的語法 */
|
| 215 |
.headless-tabs > div:first-child { display: none !important; }
|
| 216 |
.headless-tabs { border: none !important; background: transparent !important; }
|
| 217 |
"""
|
|
|
|
| 251 |
welcome_text = gr.Markdown(value="", elem_classes="text-right")
|
| 252 |
logout_btn = gr.Button("登出", size="sm")
|
| 253 |
|
| 254 |
+
# 🌟 新增:單一切換按鈕
|
| 255 |
with gr.Row():
|
| 256 |
+
btn_toggle_role = gr.Button("Student ↔ Admin", size="sm", variant="secondary")
|
| 257 |
+
|
| 258 |
gr.Markdown("---")
|
| 259 |
|
| 260 |
+
# 🌟 修改:改用 Tabs 排版並掛上 headless-tabs 樣式
|
| 261 |
with gr.Tabs(selected="student_tab", elem_classes="headless-tabs") as role_tabs:
|
| 262 |
+
with gr.Tab("student", id="student_tab"):
|
|
|
|
| 263 |
with gr.Accordion("▼ 1. 教務系統", open=True):
|
| 264 |
with gr.Row(): btn_eval = gr.Button("▶ 期末網路教學評量", size="sm"); btn_pre_select = gr.Button("▶ 期末網路預選系統", size="sm"); btn_add_drop = gr.Button("▶ 開學後加退選系統", size="sm")
|
| 265 |
with gr.Row(): btn_istudy = gr.Button("▶ 北科i學園PLUS", size="sm"); btn_card = gr.Button("▶ 學生證掛失及補發系統", size="sm"); btn_course = gr.Button("▶ 課程系統", size="sm")
|
|
|
|
| 271 |
with gr.Accordion("▼ 惡意操作測試區", open=True):
|
| 272 |
btn_hack_score = gr.Button("💀 嘗試偷改成績 (SQL Injection)", variant="stop", size="sm")
|
| 273 |
|
| 274 |
+
with gr.Tab("admin", id="admin_tab"):
|
| 275 |
with gr.Accordion("▼ 防護強度微調 (AI 敏感度)", open=True, elem_classes="weight-card"):
|
| 276 |
gr.Markdown("<span style='color:gray; font-size:0.9em;'>手動輸入 0~100 的數值。提高強度會增加誤報率,但能更有效攔截新型攻擊。</span>")
|
| 277 |
with gr.Row():
|
|
|
|
| 286 |
|
| 287 |
gr.Markdown("#### 1. 選課相關系統 (具備強烈季節性)")
|
| 288 |
with gr.Row():
|
| 289 |
+
t_pre_sel_norm = gr.Button("✅ 正常: 期末網路預選系統 (06-13 11:00)", size="sm")
|
| 290 |
+
t_pre_sel_anom = gr.Button("⚠️ 異常: 期末網路預選系統 (10-15 13:00)", size="sm")
|
| 291 |
with gr.Row():
|
| 292 |
+
t_course_norm = gr.Button("✅ 正常: 課程系統 (02-25 07:50)", size="sm")
|
| 293 |
+
t_course_anom = gr.Button("⚠️ 異常: 課程系統 (07-15 05:00)", size="sm")
|
| 294 |
with gr.Row():
|
| 295 |
+
t_add_drop_norm = gr.Button("✅ 正常: 開學後加退選系統 (02-18 14:00)", size="sm")
|
| 296 |
+
t_add_drop_anom = gr.Button("⚠️ 異常: 開學後加退選系統 (05-15 12:00)", size="sm")
|
| 297 |
|
| 298 |
gr.Markdown("#### 2. 期末與成績相關 (具備週期性)")
|
| 299 |
with gr.Row():
|
| 300 |
+
t_eval_norm = gr.Button("✅ 正常: 期末網路教學評量 (12-17 13:20)", size="sm")
|
| 301 |
+
t_eval_anom = gr.Button("⚠️ 異常: 期末網路教學評量 (03-15 03:00)", size="sm")
|
| 302 |
with gr.Row():
|
| 303 |
+
t_score_norm = gr.Button("✅ 正常: 學業成績查詢系統 (11-17 18:50)", size="sm")
|
| 304 |
+
t_score_anom = gr.Button("⚠️ 異常: ��業成績查詢系統 (02-15 03:00)", size="sm")
|
| 305 |
|
| 306 |
gr.Markdown("#### 3. 校務與行政服務 (生活與常規)")
|
| 307 |
with gr.Row():
|
| 308 |
+
t_dorm_norm = gr.Button("✅ 正常: 學生宿舍登錄(抽籤)系統 (07-21 23:10)", size="sm")
|
| 309 |
+
t_dorm_anom = gr.Button("⚠️ 異常: 學生宿舍登錄(抽籤)系統 (11-15 16:00)", size="sm")
|
| 310 |
with gr.Row():
|
| 311 |
+
t_schol_norm = gr.Button("✅ 正常: 獎助學金申請系統 (03-12 14:00)", size="sm")
|
| 312 |
+
t_schol_anom = gr.Button("⚠️ 異常: 獎助學金申請系統 (08-15 02:00)", size="sm")
|
| 313 |
with gr.Row():
|
| 314 |
+
t_card_norm = gr.Button("✅ 正常: 學生證掛失 (10-23 13:40)", size="sm")
|
| 315 |
+
t_card_anom = gr.Button("⚠️ 異常: 學生證掛失 (08-10 03:00)", size="sm")
|
| 316 |
|
| 317 |
gr.Markdown("#### 4. 學習與日常輔助系統 (具備作息規律)")
|
| 318 |
with gr.Row():
|
| 319 |
+
t_istudy_norm = gr.Button("✅ 正常: 北科i學園PLUS (10-18 16:00)", size="sm")
|
| 320 |
+
t_istudy_anom = gr.Button("⚠️ 異常: 北科i學園PLUS (08-20 02:00)", size="sm")
|
| 321 |
with gr.Row():
|
| 322 |
+
t_leave_norm = gr.Button("✅ 正常: 學生請假系統 (03-29 08:45)", size="sm")
|
| 323 |
+
t_leave_anom = gr.Button("⚠️ 異常: 學生請假系統 (08-15 13:00)", size="sm")
|
| 324 |
with gr.Row():
|
| 325 |
+
t_vdesk_norm = gr.Button("✅ 正常: 軟體雲/WebMail (03-22 18:00)", size="sm")
|
| 326 |
+
t_vdesk_anom = gr.Button("⚠️ 異常: 軟體雲/WebMail (08-10 00:00)", size="sm")
|
| 327 |
|
| 328 |
# --- 綁定事件 ---
|
| 329 |
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
|
|
|
|
| 335 |
inputs=[mock_ip, mock_cookie, acc_input, pwd_input, captcha_input, time_mode, custom_time_input] + weight_inputs,
|
| 336 |
outputs=[login_view, dashboard_view, role_tabs, login_msg, welcome_text, log_display]
|
| 337 |
)
|
|
|
|
| 338 |
logout_btn.click(
|
| 339 |
fn=logout,
|
| 340 |
inputs=[mock_ip, mock_cookie, acc_input, time_mode, custom_time_input] + weight_inputs,
|
| 341 |
+
outputs=[login_view, dashboard_view, role_tabs, acc_input, pwd_input, captcha_input, login_msg, log_display]
|
| 342 |
)
|
| 343 |
|
| 344 |
fast_inputs = [mock_ip, mock_cookie, time_mode, custom_time_input] + weight_inputs
|
| 345 |
+
fast_outputs = [login_view, dashboard_view, role_tabs, login_msg, welcome_text, log_display, acc_input]
|
|
|
|
|
|
|
| 346 |
|
| 347 |
+
btn_fast_student.click(fn=lambda ip, c, tm, ct, ow, tw, iw, dw: fast_login("student", ip, c, tm, ct, ow, tw, iw, dw), inputs=fast_inputs, outputs=fast_outputs)
|
| 348 |
+
btn_fast_admin.click(fn=lambda ip, c, tm, ct, ow, tw, iw, dw: fast_login("admin", ip, c, tm, ct, ow, tw, iw, dw), inputs=fast_inputs, outputs=fast_outputs)
|
| 349 |
+
|
| 350 |
+
# 🌟 綁定單一切換按���,把 role_tabs 當作 input 傳入判斷當前狀態
|
| 351 |
btn_toggle_role.click(
|
| 352 |
fn=toggle_role,
|
| 353 |
+
inputs=[role_tabs, mock_ip, mock_cookie, time_mode, custom_time_input] + weight_inputs,
|
| 354 |
outputs=[role_tabs, welcome_text, log_display, acc_input]
|
| 355 |
)
|
| 356 |
|
|
|
|
| 372 |
)
|
| 373 |
|
| 374 |
test_mapping = [
|
| 375 |
+
(t_pre_sel_norm, "click_pre_select_system", "2026-06-13 11:00:00"),
|
| 376 |
+
(t_pre_sel_anom, "click_pre_select_system", "2026-10-15 13:00:00"),
|
| 377 |
+
(t_course_norm, "click_course_system", "2026-02-25 07:50:00"),
|
| 378 |
+
(t_course_anom, "click_course_system", "2026-07-15 05:00:00"),
|
| 379 |
+
(t_add_drop_norm, "click_add_drop_system", "2026-02-18 14:00:00"),
|
| 380 |
+
(t_add_drop_anom, "click_add_drop_system", "2026-05-15 12:00:00"),
|
| 381 |
+
|
| 382 |
+
(t_eval_norm, "click_eval_system", "2026-12-17 13:20:00"),
|
| 383 |
+
(t_eval_anom, "click_eval_system", "2026-03-15 03:00:00"),
|
| 384 |
+
(t_score_norm, "click_score_query", "2026-11-17 18:50:00"),
|
| 385 |
+
(t_score_anom, "click_score_query", "2026-02-15 03:00:00"),
|
| 386 |
+
|
| 387 |
+
(t_dorm_norm, "click_dorm_system", "2026-07-21 23:10:00"),
|
| 388 |
+
(t_dorm_anom, "click_dorm_system", "2026-11-15 16:00:00"),
|
| 389 |
+
(t_schol_norm, "click_scholarship", "2026-03-12 14:00:00"),
|
| 390 |
+
(t_schol_anom, "click_scholarship", "2026-08-15 02:00:00"),
|
| 391 |
+
(t_card_norm, "click_card_loss", "2026-10-23 13:40:00"),
|
| 392 |
+
(t_card_anom, "click_card_loss", "2026-08-10 03:00:00"),
|
| 393 |
+
|
| 394 |
+
(t_istudy_norm, "click_istudy", "2026-10-18 16:00:00"),
|
| 395 |
+
(t_istudy_anom, "click_istudy", "2026-08-20 02:00:00"),
|
| 396 |
+
(t_leave_norm, "click_leave_system", "2026-03-29 08:45:00"),
|
| 397 |
+
(t_leave_anom, "click_leave_system", "2026-08-15 13:00:00"),
|
| 398 |
+
(t_vdesk_norm, "click_vdesk", "2026-03-22 18:00:00"),
|
| 399 |
+
(t_vdesk_anom, "click_vdesk", "2026-08-10 00:00:00")
|
| 400 |
]
|
| 401 |
|
| 402 |
for btn, action_name, test_time_str in test_mapping:
|