BrianChuan
gardio setting update
57085d4
Raw
History Blame Contribute Delete
12 kB
import gradio as gr
import datetime
import pandas as pd
import joblib
from sklearn.cluster import HDBSCAN
import numpy as np
import logger_service
# --- 1. 載入模型與歷史資料 (實現動態 Rule 的關鍵) ---
try:
le_action = joblib.load('le_action.pkl')
le_ip = joblib.load('le_ip.pkl')
# 🌟 載入歷史資料作為 HDBSCAN 即時比對的基準
baseline_df = logger_service.sync_from_hf()
baseline_df['hour'] = pd.to_datetime(baseline_df['timestamp']).dt.hour
# 預先將歷史資料轉成特徵矩陣
baseline_X = np.column_stack((
baseline_df['hour'].values,
le_ip.transform(baseline_df['ip_address'].values),
le_action.transform(baseline_df['action'].values)
))
AI_READY = True
print("✅ AI 模組與歷史基準資料載入成功!")
except Exception as e:
AI_READY = False
print(f"⚠️ AI 模組載入失敗,請確認 .pkl 與 .csv 檔案是否已上傳。錯誤: {e}")
USER_DB = {
"student": ["1234", "學生", "同學"],
"teacher": ["1234", "老師", "教授"],
"admin": ["1234", "行政", "行政人員"],
"ta": ["1234", "TA", "助教"],
"sysadmin": ["1234", "系統管理員", "管理員"]
}
log_history = baseline_df.sort_values(by='timestamp', ascending=False).head(20).to_dict('records')
# --- 3. 核心邏輯與真實 HDBSCAN 異常偵測 ---
def check_anomaly(ip, action, log_time):
if not AI_READY:
return "success"
if action in ["login_attempt", "logout"]:
return "success"
try:
# 1. 將新進來的行為轉為代碼
ip_code = le_ip.transform([ip])[0]
action_code = le_action.transform([action])[0]
# 2. 擷取當下行為的時間 (Hour)
current_hour = pd.to_datetime(log_time).hour
# 3. 組合這筆新資料的特徵向量
new_data_point = np.array([[current_hour, ip_code, action_code]])
# 4. 🌟 將新資料與歷史資料合併 (這就是流程圖中的「即時丟入 AI 進行判斷」)
combined_X = np.vstack((baseline_X, new_data_point))
# 5. 執行 HDBSCAN 分群
model = HDBSCAN(min_cluster_size=5, min_samples=3)
clusters = model.fit_predict(combined_X)
# 6. 檢查最後一筆 (也就是剛剛送進來的新動作) 是否被分類為 -1 (雜訊/異常)
if clusters[-1] == -1:
return "⚠️異常_作息或行為不符"
else:
return "success"
except ValueError:
# 如果遇到沒看過的 IP 或動作,連算都不用算,絕對是異常
return "⚠️異常_未知特徵"
def record_log(ip, cookie, account, role, action, status, time_mode, custom_time):
global baseline_X, log_history
# 決定紀錄的時間
if time_mode == "自訂時間 (模擬過去/未來)" and custom_time.strip() != "":
log_time = custom_time
else:
tw_tz = datetime.timezone(datetime.timedelta(hours=8))
log_time = datetime.datetime.now(tw_tz).strftime("%Y-%m-%d %H:%M:%S")
# 執行 AI 檢查 (現在把 log_time 也傳進去了)
if status == "success":
ai_judgment = check_anomaly(ip, action, log_time)
if ai_judgment != "success":
status = ai_judgment
new_log = {
"timestamp": log_time,
"ip_address": ip,
"cookie_id": cookie,
"account": account,
"role": role,
"action": action,
"status": status
}
try:
# 將新行為轉為特徵數值
new_hour = pd.to_datetime(log_time).hour
new_ip_code = le_ip.transform([ip])[0]
new_action_code = le_action.transform([action])[0]
new_feature = np.array([[new_hour, new_ip_code, new_action_code]])
# 🌟 把新特徵壓入全域矩陣中
baseline_X = np.vstack((baseline_X, new_feature))
except Exception as e:
print(f"⚠️ AI 矩陣更新跳過 (可能是未知特徵): {e}")
# 呼叫外部 Service 進行存檔與上傳
logger_service.push_new_log(new_log)
log_history.insert(0, new_log)
return pd.DataFrame(log_history)
def process_login(ip, cookie, account, password, captcha, time_mode, custom_time):
valid_captcha = "FEIK"
if account not in USER_DB: return gr.update(visible=True), gr.update(visible=False), "帳號不存在!", gr.update(), record_log(ip, cookie, account, "Unknown", "login_attempt", "failed_no_account", time_mode, custom_time)
role, name = USER_DB[account][1], USER_DB[account][2]
if captcha.upper() != valid_captcha: return gr.update(visible=True), gr.update(visible=False), "驗證碼錯誤!", gr.update(), record_log(ip, cookie, account, role, "login_attempt", "failed_captcha", time_mode, custom_time)
if USER_DB[account][0] == password:
welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938"
df = record_log(ip, cookie, account, role, "login_attempt", "success", time_mode, custom_time)
return gr.update(visible=False), gr.update(visible=True), "", welcome_msg, df
return gr.update(visible=True), gr.update(visible=False), "密碼錯誤!", gr.update(), record_log(ip, cookie, account, role, "login_attempt", "failed_password", time_mode, custom_time)
def simulate_click(ip, cookie, account, button_name, time_mode, custom_time):
role = USER_DB.get(account, ["", "Unknown"])[1]
return record_log(ip, cookie, account, role, button_name, "success", time_mode, custom_time)
def logout(ip, cookie, account, time_mode, custom_time):
role = USER_DB.get(account, ["", "Unknown"])[1]
df = record_log(ip, cookie, account, role, "logout", "success", time_mode, custom_time)
return gr.update(visible=True), gr.update(visible=False), "", "", "", "", df
def toggle_time_input(mode):
return gr.update(visible=(mode == "自訂時間 (模擬過去/未來)"))
# --- 4. UI 介面設計 ---
custom_css = """
.mock-panel { background-color: var(--background-fill-secondary); padding: 15px; border-radius: 8px; border: 1px dashed var(--border-color-primary); }
.login-container { max-width: 500px; margin: 0 auto; padding: 20px; }
.placeholder-box { background-color: var(--background-fill-secondary); border: 2px dashed var(--border-color-primary); text-align: center; color: var(--body-text-color); padding: 40px 0; margin-bottom: 15px; border-radius: 5px; }
.ntut-title { color: #d9534f; font-size: 22px; font-weight: bold; text-align: center; margin-bottom: 20px;}
"""
with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css) as demo:
with gr.Row():
with gr.Column(scale=1, elem_classes="mock-panel"):
gr.Markdown("### ⚙️ 環境變數模擬器")
mock_ip = gr.Dropdown(choices=["140.124.71.55 (校內預設)", "140.124.18.22 (宿舍)", "45.33.22.11 (國外異常IP)"], value="140.124.71.55 (校內預設)", allow_custom_value=True, label="模擬來源 IP")
mock_cookie = gr.Textbox(label="模擬 Cookie Session ID", value="sess_baseline")
gr.Markdown("---")
time_mode = gr.Radio(label="時間設定模式", choices=["真實時間 (目前時間)", "自訂時間 (模擬過去/未來)"], value="真實時間 (目前時間)")
custom_time_input = gr.Textbox(label="輸入自訂時間", placeholder="格式: YYYY-MM-DD HH:MM:SS", value="2026-03-17 03:00:00", visible=False)
gr.Markdown("---")
gr.Markdown("### 📊 即時 Log 與 AI 判定區")
log_display = gr.Dataframe(value=pd.DataFrame(log_history),headers=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"], datatype=["str", "str", "str", "str", "str", "str", "str"], interactive=False, wrap=True)
with gr.Column(scale=2):
with gr.Column(visible=True, elem_classes="login-container") as login_view:
gr.HTML("<div class='placeholder-box'>預留位置:台北科大 Logo 與 e化校園圖片</div>")
gr.Markdown("<div class='ntut-title'>校園入口網站 Taipei Tech Portal</div>")
login_msg = gr.Markdown(value="", visible=True)
with gr.Row(): acc_input = gr.Textbox(label="使用者帳號 (Account)", placeholder="例如: student, admin...")
with gr.Row(): pwd_input = gr.Textbox(label="使用者密碼 (Password)", type="password", placeholder="密碼皆為 1234")
with gr.Row(): gr.HTML("<div style='background:var(--background-fill-secondary); color:var(--body-text-color); padding:10px; font-weight:bold; letter-spacing: 3px; border:1px solid var(--border-color-primary); text-align:center;'>F E I K</div>")
with gr.Row(): captcha_input = gr.Textbox(label="請輸入驗證碼 (Keyin Code)")
login_btn = gr.Button("登入 Login", variant="primary")
with gr.Column(visible=False) as dashboard_view:
gr.HTML("<div class='placeholder-box'>預留位置:學生學習歷程入口網站橫幅</div>")
with gr.Row():
gr.Markdown("### 資訊系統")
welcome_text = gr.Markdown(value="", elem_classes="text-right")
logout_btn = gr.Button("登出", size="sm")
gr.Markdown("---")
with gr.Accordion("▼ 1. 教務系統", open=True):
with gr.Row(): btn_eval = gr.Button("▶ 期末網路教學評量", size="sm"); btn_pre_select = gr.Button("▶ 期末網路預選系統", size="sm"); btn_add_drop = gr.Button("▶ 開學後加退選系統", size="sm")
with gr.Row(): btn_istudy = gr.Button("▶ 北科i學園PLUS", size="sm"); btn_card = gr.Button("▶ 學生證掛失及補發系統", size="sm"); btn_course = gr.Button("▶ 課程系統", size="sm")
with gr.Row(): btn_score = gr.Button("▶ 學業成績查詢系統", size="sm")
with gr.Accordion("▼ 2. 學務系統", open=True):
with gr.Row(): btn_leave = gr.Button("▶ 學生請假系統", size="sm"); btn_dorm = gr.Button("▶ 學生宿舍登錄(抽籤)系統", size="sm"); btn_scholarship = gr.Button("▶ 獎助學金申請系統", size="sm")
with gr.Accordion("▼ 3. 資訊服務", open=True):
with gr.Row(): btn_webmail = gr.Button("▶ 網路郵局 WebMail", size="sm"); btn_vdesk = gr.Button("▶ 北科軟體雲", size="sm")
with gr.Accordion("▼ 惡意操作測試區", open=True):
btn_hack_score = gr.Button("💀 嘗試偷改成績 (SQL Injection)", variant="stop", size="sm")
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
login_btn.click(fn=process_login, inputs=[mock_ip, mock_cookie, acc_input, pwd_input, captcha_input, time_mode, custom_time_input], outputs=[login_view, dashboard_view, login_msg, welcome_text, log_display])
logout_btn.click(fn=logout, inputs=[mock_ip, mock_cookie, acc_input, time_mode, custom_time_input], outputs=[login_view, dashboard_view, acc_input, pwd_input, captcha_input, login_msg, log_display])
for btn, action_name in [(btn_eval, "click_eval_system"), (btn_pre_select, "click_pre_select_system"), (btn_add_drop, "click_add_drop_system"), (btn_istudy, "click_istudy"), (btn_card, "click_card_loss"), (btn_course, "click_course_system"), (btn_score, "click_score_query"), (btn_leave, "click_leave_system"), (btn_dorm, "click_dorm_system"), (btn_scholarship, "click_scholarship"), (btn_webmail, "click_webmail"), (btn_vdesk, "click_vdesk"), (btn_hack_score, "malicious_sql_injection")]:
btn.click(fn=lambda ip, c, acc, t_mode, t_custom, n=action_name: simulate_click(ip, c, acc, n, t_mode, t_custom), inputs=[mock_ip, mock_cookie, acc_input, time_mode, custom_time_input], outputs=[log_display])
if __name__ == "__main__":
demo.launch(allowed_paths=["*"])