Update app.py
Browse files
app.py
CHANGED
|
@@ -44,47 +44,70 @@ def check_anomaly(ip, action, log_time, overall_w, time_w, ip_w, dev_w):
|
|
| 44 |
if action in ["login_attempt", "logout"]: return "success"
|
| 45 |
|
| 46 |
try:
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
current_hour = current_time.hour
|
| 55 |
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
scaled_baseline = baseline_X.copy().astype(float)
|
| 59 |
-
scaled_baseline[:, 0] *=
|
| 60 |
-
scaled_baseline[:, 1] *=
|
| 61 |
-
scaled_baseline[:, 2] *=
|
| 62 |
-
scaled_baseline[:, 3] *= (
|
| 63 |
scaled_baseline[:, 4] *= action_scale
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
]])
|
| 72 |
|
| 73 |
combined_X = np.vstack((scaled_baseline, new_data_point))
|
| 74 |
-
strictness = max(2, int(2 + (
|
| 75 |
|
| 76 |
-
model = HDBSCAN(
|
|
|
|
|
|
|
|
|
|
| 77 |
clusters = model.fit_predict(combined_X)
|
| 78 |
|
| 79 |
-
if clusters[-1] == -1:
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
def record_log(ip, cookie, account, role, action, status, time_mode, custom_time, ow, tw, iw, dw):
|
| 85 |
global baseline_X
|
| 86 |
-
if time_mode == "自訂時間 (模擬過去/未來)" and custom_time.strip() != "":
|
| 87 |
-
log_time = custom_time
|
| 88 |
else:
|
| 89 |
tw_tz = datetime.timezone(datetime.timedelta(hours=8))
|
| 90 |
log_time = datetime.datetime.now(tw_tz).strftime("%Y-%m-%d %H:%M:%S")
|
|
@@ -103,18 +126,18 @@ def record_log(ip, cookie, account, role, action, status, time_mode, custom_time
|
|
| 103 |
try:
|
| 104 |
pd.DataFrame([new_log]).to_csv('baseline_logs.csv', mode='a', header=False, index=False, encoding='utf-8-sig')
|
| 105 |
|
| 106 |
-
current_time = pd.to_datetime(log_time)
|
| 107 |
-
clean_ip = ip.split(" ")[0]
|
| 108 |
new_row = np.array([[
|
| 109 |
-
current_time.month,
|
| 110 |
-
current_time.day,
|
| 111 |
-
current_time.hour,
|
| 112 |
-
le_ip.transform([clean_ip])[0],
|
| 113 |
-
le_action.transform([action])[0]
|
| 114 |
-
]])
|
| 115 |
baseline_X = np.vstack((baseline_X, new_row))
|
| 116 |
except Exception as e:
|
| 117 |
-
|
| 118 |
|
| 119 |
return pd.DataFrame(log_history)
|
| 120 |
|
|
@@ -158,10 +181,8 @@ def simulate_click(ip, cookie, account, button_name, time_mode, custom_time, ow,
|
|
| 158 |
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 159 |
return record_log(ip, cookie, account, role, button_name, "success", time_mode, custom_time, ow, tw, iw, dw)
|
| 160 |
|
| 161 |
-
# 🌟 新增:管理員專用的快速測試函式 (強制寫入指定的時間)
|
| 162 |
def test_anomaly(ip, cookie, account, action_name, test_time_str, ow, tw, iw, dw):
|
| 163 |
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 164 |
-
# 強制將 time_mode 設為自訂,並傳入測試的具體時間
|
| 165 |
return record_log(ip, cookie, account, role, action_name, "success", "自訂時間 (模擬過去/未來)", test_time_str, ow, tw, iw, dw)
|
| 166 |
|
| 167 |
def logout(ip, cookie, account, time_mode, custom_time, ow, tw, iw, dw):
|
|
@@ -240,50 +261,50 @@ with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css)
|
|
| 240 |
ip_weight = gr.Number(label="IP 聲譽權重 (%)", value=65, minimum=0, maximum=100)
|
| 241 |
device_weight = gr.Number(label="裝置指紋/行為權重 (%)", value=45, minimum=0, maximum=100)
|
| 242 |
|
| 243 |
-
# 🌟 新增:HDBSCAN 自動化測試面板
|
| 244 |
with gr.Accordion("▼ HDBSCAN 異常辨識自動化測試區", open=True, elem_classes="weight-card"):
|
| 245 |
gr.Markdown("<span style='color:gray; font-size:0.9em;'>點擊按鈕將直接以指定時間點執行該系統的操作,結果將顯示於左側 Log 區。</span>")
|
| 246 |
|
|
|
|
| 247 |
gr.Markdown("#### 1. 選課相關系統 (具備強烈季節性)")
|
| 248 |
with gr.Row():
|
| 249 |
-
t_pre_sel_norm = gr.Button("✅ 正常:
|
| 250 |
-
t_pre_sel_anom = gr.Button("⚠️ 異常:
|
| 251 |
with gr.Row():
|
| 252 |
-
t_course_norm = gr.Button("✅ 正常: 課
|
| 253 |
-
t_course_anom = gr.Button("⚠️ 異常: 課
|
| 254 |
with gr.Row():
|
| 255 |
-
t_add_drop_norm = gr.Button("✅ 正常:
|
| 256 |
-
t_add_drop_anom = gr.Button("⚠️ 異常:
|
| 257 |
|
| 258 |
gr.Markdown("#### 2. 期末與成績相關 (具備週期性)")
|
| 259 |
with gr.Row():
|
| 260 |
-
t_eval_norm = gr.Button("✅ 正常:
|
| 261 |
-
t_eval_anom = gr.Button("⚠️ 異常:
|
| 262 |
with gr.Row():
|
| 263 |
-
t_score_norm = gr.Button("✅ 正常:
|
| 264 |
-
t_score_anom = gr.Button("⚠️ 異常:
|
| 265 |
|
| 266 |
gr.Markdown("#### 3. 校務與行政服務 (生活與常規)")
|
| 267 |
with gr.Row():
|
| 268 |
-
t_dorm_norm = gr.Button("✅ 正常:
|
| 269 |
-
t_dorm_anom = gr.Button("⚠️ 異常:
|
| 270 |
with gr.Row():
|
| 271 |
-
t_schol_norm = gr.Button("✅ 正常: 獎
|
| 272 |
-
t_schol_anom = gr.Button("⚠️ 異常: 獎
|
| 273 |
with gr.Row():
|
| 274 |
-
t_card_norm = gr.Button("✅ 正常: 學生證掛失 (
|
| 275 |
-
t_card_anom = gr.Button("⚠️ 異常: 學生證掛失 (
|
| 276 |
|
| 277 |
gr.Markdown("#### 4. 學習與日常輔助系統 (具備作息規律)")
|
| 278 |
with gr.Row():
|
| 279 |
-
t_istudy_norm = gr.Button("✅ 正常:
|
| 280 |
-
t_istudy_anom = gr.Button("⚠️ 異常:
|
| 281 |
with gr.Row():
|
| 282 |
-
t_leave_norm = gr.Button("✅ 正常:
|
| 283 |
-
t_leave_anom = gr.Button("⚠️ 異常:
|
| 284 |
with gr.Row():
|
| 285 |
-
t_vdesk_norm = gr.Button("✅ 正常: 軟體雲/
|
| 286 |
-
t_vdesk_anom = gr.Button("⚠️ 異常: 軟體雲/
|
| 287 |
|
| 288 |
# --- 綁定事件 ---
|
| 289 |
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
|
|
@@ -326,37 +347,33 @@ with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css)
|
|
| 326 |
outputs=[log_display]
|
| 327 |
)
|
| 328 |
|
| 329 |
-
# 🌟
|
| 330 |
test_mapping = [
|
| 331 |
-
|
| 332 |
-
(
|
| 333 |
-
(
|
| 334 |
-
(
|
| 335 |
-
(
|
| 336 |
-
(
|
| 337 |
-
(t_add_drop_anom, "click_add_drop_system", "2026-04-10 21:00:00"),
|
| 338 |
|
| 339 |
-
|
| 340 |
-
(
|
| 341 |
-
(
|
| 342 |
-
(
|
| 343 |
-
(t_score_anom, "click_score_query", "2026-08-15 10:00:00"),
|
| 344 |
|
| 345 |
-
|
| 346 |
-
(
|
| 347 |
-
(
|
| 348 |
-
(
|
| 349 |
-
(
|
| 350 |
-
(
|
| 351 |
-
(t_card_anom, "click_card_loss", "2026-10-01 04:00:00"),
|
| 352 |
|
| 353 |
-
|
| 354 |
-
(
|
| 355 |
-
(
|
| 356 |
-
(
|
| 357 |
-
(
|
| 358 |
-
(
|
| 359 |
-
(t_vdesk_anom, "click_vdesk", "2026-11-10 04:00:00")
|
| 360 |
]
|
| 361 |
|
| 362 |
for btn, action_name, test_time_str in test_mapping:
|
|
|
|
| 44 |
if action in ["login_attempt", "logout"]: return "success"
|
| 45 |
|
| 46 |
try:
|
| 47 |
+
ow = float(overall_w)
|
| 48 |
+
tw = float(time_w)
|
| 49 |
+
iw = float(ip_w)
|
| 50 |
+
dw = float(dev_w)
|
| 51 |
|
| 52 |
+
clean_ip = str(ip).split(" ")[0]
|
| 53 |
+
ip_code = float(le_ip.transform([clean_ip])[0])
|
| 54 |
+
action_code = float(le_action.transform([action])[0])
|
|
|
|
| 55 |
|
| 56 |
+
current_time = pd.to_datetime(str(log_time))
|
| 57 |
+
current_month = float(current_time.month)
|
| 58 |
+
current_day = float(current_time.day)
|
| 59 |
+
current_hour = float(current_time.hour)
|
| 60 |
+
|
| 61 |
+
time_multiplier = tw / 100.0
|
| 62 |
+
action_scale = (dw / 100.0) * 100.0
|
| 63 |
|
| 64 |
scaled_baseline = baseline_X.copy().astype(float)
|
| 65 |
+
scaled_baseline[:, 0] *= time_multiplier * 10.0
|
| 66 |
+
scaled_baseline[:, 1] *= time_multiplier * 0.5
|
| 67 |
+
scaled_baseline[:, 2] *= time_multiplier * 2.0
|
| 68 |
+
scaled_baseline[:, 3] *= (iw / 100.0) * 10.0
|
| 69 |
scaled_baseline[:, 4] *= action_scale
|
| 70 |
|
| 71 |
+
v_month = current_month * time_multiplier * 10.0
|
| 72 |
+
v_day = current_day * time_multiplier * 0.5
|
| 73 |
+
v_hour = current_hour * time_multiplier * 2.0
|
| 74 |
+
v_ip = ip_code * (iw / 100.0) * 10.0
|
| 75 |
+
v_action = action_code * action_scale
|
| 76 |
+
|
| 77 |
+
new_data_point = np.array([[v_month, v_day, v_hour, v_ip, v_action]], dtype=float)
|
| 78 |
|
| 79 |
combined_X = np.vstack((scaled_baseline, new_data_point))
|
| 80 |
+
strictness = max(2, int(2 + (ow / 100.0) * 4))
|
| 81 |
|
| 82 |
+
model = HDBSCAN(
|
| 83 |
+
min_cluster_size=strictness,
|
| 84 |
+
min_samples=2
|
| 85 |
+
)
|
| 86 |
clusters = model.fit_predict(combined_X)
|
| 87 |
|
| 88 |
+
if clusters[-1] == -1:
|
| 89 |
+
return "⚠️異常_作息或行為不符"
|
| 90 |
+
|
| 91 |
+
same_action_mask = (baseline_X[:, 4] == action_code)
|
| 92 |
+
|
| 93 |
+
if np.any(same_action_mask):
|
| 94 |
+
history_points = scaled_baseline[same_action_mask]
|
| 95 |
+
distances = np.linalg.norm(history_points - new_data_point[0], axis=1)
|
| 96 |
+
min_distance = np.min(distances)
|
| 97 |
+
|
| 98 |
+
max_tolerance_distance = time_multiplier * 15.0 + 1.0
|
| 99 |
+
|
| 100 |
+
if min_distance > max_tolerance_distance:
|
| 101 |
+
return "⚠️異常_作息或行為不符"
|
| 102 |
+
|
| 103 |
+
return "success"
|
| 104 |
+
except Exception as e:
|
| 105 |
+
return f"⚠️系統錯誤: {str(e)}"
|
| 106 |
|
| 107 |
def record_log(ip, cookie, account, role, action, status, time_mode, custom_time, ow, tw, iw, dw):
|
| 108 |
global baseline_X
|
| 109 |
+
if time_mode == "自訂時間 (模擬過去/未來)" and custom_time and str(custom_time).strip() != "":
|
| 110 |
+
log_time = str(custom_time)
|
| 111 |
else:
|
| 112 |
tw_tz = datetime.timezone(datetime.timedelta(hours=8))
|
| 113 |
log_time = datetime.datetime.now(tw_tz).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
| 126 |
try:
|
| 127 |
pd.DataFrame([new_log]).to_csv('baseline_logs.csv', mode='a', header=False, index=False, encoding='utf-8-sig')
|
| 128 |
|
| 129 |
+
current_time = pd.to_datetime(str(log_time))
|
| 130 |
+
clean_ip = str(ip).split(" ")[0]
|
| 131 |
new_row = np.array([[
|
| 132 |
+
float(current_time.month),
|
| 133 |
+
float(current_time.day),
|
| 134 |
+
float(current_time.hour),
|
| 135 |
+
float(le_ip.transform([clean_ip])[0]),
|
| 136 |
+
float(le_action.transform([action])[0])
|
| 137 |
+
]], dtype=float)
|
| 138 |
baseline_X = np.vstack((baseline_X, new_row))
|
| 139 |
except Exception as e:
|
| 140 |
+
pass
|
| 141 |
|
| 142 |
return pd.DataFrame(log_history)
|
| 143 |
|
|
|
|
| 181 |
role = USER_DB.get(account, ["", "Unknown"])[1]
|
| 182 |
return record_log(ip, cookie, account, role, button_name, "success", time_mode, custom_time, ow, tw, iw, dw)
|
| 183 |
|
|
|
|
| 184 |
def test_anomaly(ip, cookie, account, action_name, test_time_str, ow, tw, iw, dw):
|
| 185 |
role = USER_DB.get(account, ["", "Unknown"])[1]
|
|
|
|
| 186 |
return record_log(ip, cookie, account, role, action_name, "success", "自訂時間 (模擬過去/未來)", test_time_str, ow, tw, iw, dw)
|
| 187 |
|
| 188 |
def logout(ip, cookie, account, time_mode, custom_time, ow, tw, iw, dw):
|
|
|
|
| 261 |
ip_weight = gr.Number(label="IP 聲譽權重 (%)", value=65, minimum=0, maximum=100)
|
| 262 |
device_weight = gr.Number(label="裝置指紋/行為權重 (%)", value=45, minimum=0, maximum=100)
|
| 263 |
|
|
|
|
| 264 |
with gr.Accordion("▼ HDBSCAN 異常辨識自動化測試區", open=True, elem_classes="weight-card"):
|
| 265 |
gr.Markdown("<span style='color:gray; font-size:0.9em;'>點擊按鈕將直接以指定時間點執行該系統的操作,結果將顯示於左側 Log 區。</span>")
|
| 266 |
|
| 267 |
+
# 🌟 按鈕文字已更新為最新測試時間 🌟
|
| 268 |
gr.Markdown("#### 1. 選課相關系統 (具備強烈季節性)")
|
| 269 |
with gr.Row():
|
| 270 |
+
t_pre_sel_norm = gr.Button("✅ 正常: 初選系統 (06-13 11:00)", size="sm")
|
| 271 |
+
t_pre_sel_anom = gr.Button("⚠️ 異常: 初選系統 (10-15 13:00)", size="sm")
|
| 272 |
with gr.Row():
|
| 273 |
+
t_course_norm = gr.Button("✅ 正常: 選課系統 (02-25 07:50)", size="sm")
|
| 274 |
+
t_course_anom = gr.Button("⚠️ 異常: 選課系統 (07-15 05:00)", size="sm")
|
| 275 |
with gr.Row():
|
| 276 |
+
t_add_drop_norm = gr.Button("✅ 正常: 加退選系統 (02-18 01:00)", size="sm")
|
| 277 |
+
t_add_drop_anom = gr.Button("⚠️ 異常: 加退選系統 (05-15 12:00)", size="sm")
|
| 278 |
|
| 279 |
gr.Markdown("#### 2. 期末與成績相關 (具備週期性)")
|
| 280 |
with gr.Row():
|
| 281 |
+
t_eval_norm = gr.Button("✅ 正常: 教學評鑑 (12-17 13:20)", size="sm")
|
| 282 |
+
t_eval_anom = gr.Button("⚠️ 異常: 教學評鑑 (03-15 03:00)", size="sm")
|
| 283 |
with gr.Row():
|
| 284 |
+
t_score_norm = gr.Button("✅ 正常: 成績查詢 (11-17 18:50)", size="sm")
|
| 285 |
+
t_score_anom = gr.Button("⚠️ 異常: 成績查詢 (02-15 03:00)", size="sm")
|
| 286 |
|
| 287 |
gr.Markdown("#### 3. 校務與行政服務 (生活與常規)")
|
| 288 |
with gr.Row():
|
| 289 |
+
t_dorm_norm = gr.Button("✅ 正常: 宿舍系統 (07-21 23:10)", size="sm")
|
| 290 |
+
t_dorm_anom = gr.Button("⚠️ 異常: 宿舍系統 (11-15 16:00)", size="sm")
|
| 291 |
with gr.Row():
|
| 292 |
+
t_schol_norm = gr.Button("✅ 正常: 獎學金申請 (03-12 14:00)", size="sm")
|
| 293 |
+
t_schol_anom = gr.Button("⚠️ 異常: 獎學金申請 (08-15 02:00)", size="sm")
|
| 294 |
with gr.Row():
|
| 295 |
+
t_card_norm = gr.Button("✅ 正常: 學生證掛失 (10-23 13:40)", size="sm")
|
| 296 |
+
t_card_anom = gr.Button("⚠️ 異常: 學生證掛失 (08-10 03:00)", size="sm")
|
| 297 |
|
| 298 |
gr.Markdown("#### 4. 學習與日常輔助系統 (具備作息規律)")
|
| 299 |
with gr.Row():
|
| 300 |
+
t_istudy_norm = gr.Button("✅ 正常: iStudy 平台 (10-18 16:00)", size="sm")
|
| 301 |
+
t_istudy_anom = gr.Button("⚠️ 異常: iStudy 平台 (08-20 02:00)", size="sm")
|
| 302 |
with gr.Row():
|
| 303 |
+
t_leave_norm = gr.Button("✅ 正常: 請假系統 (03-29 08:45)", size="sm")
|
| 304 |
+
t_leave_anom = gr.Button("⚠️ 異常: 請假系統 (08-15 13:00)", size="sm")
|
| 305 |
with gr.Row():
|
| 306 |
+
t_vdesk_norm = gr.Button("✅ 正常: 軟體雲/信箱 (03-22 18:00)", size="sm")
|
| 307 |
+
t_vdesk_anom = gr.Button("⚠️ 異常: 軟體雲/信箱 (08-10 00:00)", size="sm")
|
| 308 |
|
| 309 |
# --- 綁定事件 ---
|
| 310 |
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
|
|
|
|
| 347 |
outputs=[log_display]
|
| 348 |
)
|
| 349 |
|
| 350 |
+
# 🌟 內部參數 Mapping 已更新為最新測試時間 🌟
|
| 351 |
test_mapping = [
|
| 352 |
+
(t_pre_sel_norm, "click_pre_select_system", "2026-06-13 11:00:00"),
|
| 353 |
+
(t_pre_sel_anom, "click_pre_select_system", "2026-10-15 13:00:00"),
|
| 354 |
+
(t_course_norm, "click_course_system", "2026-02-25 07:50:00"),
|
| 355 |
+
(t_course_anom, "click_course_system", "2026-07-15 05:00:00"),
|
| 356 |
+
(t_add_drop_norm, "click_add_drop_system", "2026-02-18 01:00:00"),
|
| 357 |
+
(t_add_drop_anom, "click_add_drop_system", "2026-05-15 12:00:00"),
|
|
|
|
| 358 |
|
| 359 |
+
(t_eval_norm, "click_eval_system", "2026-12-17 13:20:00"),
|
| 360 |
+
(t_eval_anom, "click_eval_system", "2026-03-15 03:00:00"),
|
| 361 |
+
(t_score_norm, "click_score_query", "2026-11-17 18:50:00"),
|
| 362 |
+
(t_score_anom, "click_score_query", "2026-02-15 03:00:00"),
|
|
|
|
| 363 |
|
| 364 |
+
(t_dorm_norm, "click_dorm_system", "2026-07-21 23:10:00"),
|
| 365 |
+
(t_dorm_anom, "click_dorm_system", "2026-11-15 16:00:00"),
|
| 366 |
+
(t_schol_norm, "click_scholarship", "2026-03-12 14:00:00"),
|
| 367 |
+
(t_schol_anom, "click_scholarship", "2026-08-15 02:00:00"),
|
| 368 |
+
(t_card_norm, "click_card_loss", "2026-10-23 13:40:00"),
|
| 369 |
+
(t_card_anom, "click_card_loss", "2026-08-10 03:00:00"),
|
|
|
|
| 370 |
|
| 371 |
+
(t_istudy_norm, "click_istudy", "2026-10-18 16:00:00"),
|
| 372 |
+
(t_istudy_anom, "click_istudy", "2026-08-20 02:00:00"),
|
| 373 |
+
(t_leave_norm, "click_leave_system", "2026-03-29 08:45:00"),
|
| 374 |
+
(t_leave_anom, "click_leave_system", "2026-08-15 13:00:00"),
|
| 375 |
+
(t_vdesk_norm, "click_vdesk", "2026-03-22 18:00:00"),
|
| 376 |
+
(t_vdesk_anom, "click_vdesk", "2026-08-10 00:00:00")
|
|
|
|
| 377 |
]
|
| 378 |
|
| 379 |
for btn, action_name, test_time_str in test_mapping:
|