Spaces:
Sleeping
Sleeping
for API
Browse files
app.py
CHANGED
|
@@ -73,15 +73,14 @@ def get_ip_category(ip_str):
|
|
| 73 |
elif ip.startswith("45.33."): return 3.0
|
| 74 |
else: return 4.0
|
| 75 |
|
| 76 |
-
# 👉 新增:專供 Geo-Weight 權重邏輯使用的精細分級
|
| 77 |
def get_geo_level(ip_str):
|
| 78 |
ip = str(ip_str).split(" ")[0]
|
| 79 |
-
if ip == "140.124.71.55": return 0
|
| 80 |
-
elif ip == "140.124.18.22": return 1
|
| 81 |
-
elif ip.startswith("61.228."): return 2
|
| 82 |
-
elif ip.startswith("210.61."): return 3
|
| 83 |
-
elif ip.startswith("45.33."): return 4
|
| 84 |
-
else: return 5
|
| 85 |
|
| 86 |
# --- 1. 載入模型與歷史資料 ---
|
| 87 |
try:
|
|
@@ -136,6 +135,74 @@ except Exception as e:
|
|
| 136 |
|
| 137 |
USER_DB = {"student": ["1234", "學生", "同學"], "admin": ["1234", "管理", "管理員"]}
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
def get_dashboard_html():
|
| 140 |
total = global_stats["total_count"]
|
| 141 |
abnormal = global_stats["abnormal_count"]
|
|
@@ -277,7 +344,6 @@ def check_anomaly(ip, account, action, log_time):
|
|
| 277 |
gw = float(system_weights["gw"])
|
| 278 |
fw = float(system_weights["fw"])
|
| 279 |
|
| 280 |
-
# 👉 第一道防線:地理圍籬 (Geo-Fencing) 攔截
|
| 281 |
geo_level = get_geo_level(ip)
|
| 282 |
|
| 283 |
if gw == 100 and geo_level > 0:
|
|
@@ -288,9 +354,7 @@ def check_anomaly(ip, account, action, log_time):
|
|
| 288 |
return "⚠️異常_地理位置受限 (進階防護: 禁止公共場所與海外連線)"
|
| 289 |
elif gw >= 50 and geo_level > 3:
|
| 290 |
return "⚠️異常_地理位置受限 (預設防護: 禁止海外異常 IP)"
|
| 291 |
-
# 若 gw < 50,則放行所有位置,交由後續 AI 處理
|
| 292 |
|
| 293 |
-
# --- 第二道防線:頻率攔截 ---
|
| 294 |
current_dt = pd.to_datetime(str(log_time))
|
| 295 |
cutoff_dt = current_dt - pd.Timedelta(seconds=60)
|
| 296 |
|
|
@@ -318,7 +382,6 @@ def check_anomaly(ip, account, action, log_time):
|
|
| 318 |
if (recent_clicks + 1) > max_allowed_clicks:
|
| 319 |
return f"⚠️異常_單一操作頻率過高 ({recent_clicks+1}次/分)"
|
| 320 |
|
| 321 |
-
# --- 第三道防線:HDBSCAN AI 空間異常判斷 ---
|
| 322 |
ip_code = float(get_ip_category(ip))
|
| 323 |
action_code = float(le_action.transform([action])[0])
|
| 324 |
|
|
@@ -655,5 +718,24 @@ with gr.Blocks(title="模擬校園入口與資安防護系統") as demo:
|
|
| 655 |
outputs=[store_df_all, store_df_abn, abnormal_log_input]
|
| 656 |
)
|
| 657 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 658 |
if __name__ == "__main__":
|
| 659 |
demo.launch(css=custom_css)
|
|
|
|
| 73 |
elif ip.startswith("45.33."): return 3.0
|
| 74 |
else: return 4.0
|
| 75 |
|
|
|
|
| 76 |
def get_geo_level(ip_str):
|
| 77 |
ip = str(ip_str).split(" ")[0]
|
| 78 |
+
if ip == "140.124.71.55": return 0
|
| 79 |
+
elif ip == "140.124.18.22": return 1
|
| 80 |
+
elif ip.startswith("61.228."): return 2
|
| 81 |
+
elif ip.startswith("210.61."): return 3
|
| 82 |
+
elif ip.startswith("45.33."): return 4
|
| 83 |
+
else: return 5
|
| 84 |
|
| 85 |
# --- 1. 載入模型與歷史資料 ---
|
| 86 |
try:
|
|
|
|
| 135 |
|
| 136 |
USER_DB = {"student": ["1234", "學生", "同學"], "admin": ["1234", "管理", "管理員"]}
|
| 137 |
|
| 138 |
+
|
| 139 |
+
# =====================================================================
|
| 140 |
+
# 👉 新增:純淨 API 端點專用函式 (只回傳 JSON 字典,給隊友抓資料用)
|
| 141 |
+
# =====================================================================
|
| 142 |
+
def api_get_stats():
|
| 143 |
+
"""取得系統狀態與權重"""
|
| 144 |
+
total = global_stats["total_count"]
|
| 145 |
+
abnormal = global_stats["abnormal_count"]
|
| 146 |
+
rate = round((abnormal / total * 100), 2) if total > 0 else 0
|
| 147 |
+
return {
|
| 148 |
+
"status": "success",
|
| 149 |
+
"ai_ready": AI_READY,
|
| 150 |
+
"data": {
|
| 151 |
+
"total_logs": total,
|
| 152 |
+
"abnormal_logs": abnormal,
|
| 153 |
+
"anomaly_rate_percent": rate,
|
| 154 |
+
"current_weights": system_weights
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
def api_get_logs(limit=20):
|
| 159 |
+
"""取得最新的 N 筆 Log"""
|
| 160 |
+
try: limit = int(limit)
|
| 161 |
+
except: limit = 20
|
| 162 |
+
return {
|
| 163 |
+
"status": "success",
|
| 164 |
+
"count": len(log_history[:limit]),
|
| 165 |
+
"data": log_history[:limit]
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
def api_get_chart_data(action_name):
|
| 169 |
+
"""取得特定系統的圖表原始數據 (供前端自行繪圖)"""
|
| 170 |
+
if not AI_READY: return {"status": "error", "message": "AI 模型未載入"}
|
| 171 |
+
action_code = ACTION_MAP.get(action_name)
|
| 172 |
+
if not action_code: return {"status": "error", "message": f"找不到系統: {action_name}"}
|
| 173 |
+
|
| 174 |
+
df_hist = baseline_df[baseline_df['action'] == action_code].copy()
|
| 175 |
+
df_live = pd.DataFrame(log_history)
|
| 176 |
+
if not df_live.empty:
|
| 177 |
+
df_live['timestamp'] = pd.to_datetime(df_live['timestamp'], errors='coerce')
|
| 178 |
+
df_live = df_live.dropna(subset=['timestamp'])
|
| 179 |
+
df_live = df_live[df_live['action'] == action_code].copy()
|
| 180 |
+
|
| 181 |
+
if not df_live.empty:
|
| 182 |
+
df_combined = pd.concat([df_hist, df_live], ignore_index=True)
|
| 183 |
+
df_combined = df_combined.drop_duplicates(subset=['timestamp', 'account', 'action'])
|
| 184 |
+
else:
|
| 185 |
+
df_combined = df_hist.copy()
|
| 186 |
+
|
| 187 |
+
if df_combined.empty:
|
| 188 |
+
return {"status": "success", "action": action_code, "data": {"normal": [], "abnormal": []}}
|
| 189 |
+
|
| 190 |
+
df_combined['month'] = df_combined['timestamp'].dt.month
|
| 191 |
+
df_combined['time_of_day'] = df_combined['timestamp'].dt.hour + df_combined['timestamp'].dt.minute / 60.0
|
| 192 |
+
|
| 193 |
+
is_abnormal = df_combined['status'].astype(str).str.contains("異常", na=False)
|
| 194 |
+
|
| 195 |
+
normal_data = df_combined[~is_abnormal][['month', 'time_of_day', 'timestamp']].astype(str).to_dict('records')
|
| 196 |
+
abnormal_data = df_combined[is_abnormal][['month', 'time_of_day', 'timestamp', 'status']].astype(str).to_dict('records')
|
| 197 |
+
|
| 198 |
+
return {
|
| 199 |
+
"status": "success",
|
| 200 |
+
"action_code": action_code,
|
| 201 |
+
"data": {"normal": normal_data, "abnormal": abnormal_data}
|
| 202 |
+
}
|
| 203 |
+
# =====================================================================
|
| 204 |
+
|
| 205 |
+
|
| 206 |
def get_dashboard_html():
|
| 207 |
total = global_stats["total_count"]
|
| 208 |
abnormal = global_stats["abnormal_count"]
|
|
|
|
| 344 |
gw = float(system_weights["gw"])
|
| 345 |
fw = float(system_weights["fw"])
|
| 346 |
|
|
|
|
| 347 |
geo_level = get_geo_level(ip)
|
| 348 |
|
| 349 |
if gw == 100 and geo_level > 0:
|
|
|
|
| 354 |
return "⚠️異常_地理位置受限 (進階防護: 禁止公共場所與海外連線)"
|
| 355 |
elif gw >= 50 and geo_level > 3:
|
| 356 |
return "⚠️異常_地理位置受限 (預設防護: 禁止海外異常 IP)"
|
|
|
|
| 357 |
|
|
|
|
| 358 |
current_dt = pd.to_datetime(str(log_time))
|
| 359 |
cutoff_dt = current_dt - pd.Timedelta(seconds=60)
|
| 360 |
|
|
|
|
| 382 |
if (recent_clicks + 1) > max_allowed_clicks:
|
| 383 |
return f"⚠️異常_單一操作頻率過高 ({recent_clicks+1}次/分)"
|
| 384 |
|
|
|
|
| 385 |
ip_code = float(get_ip_category(ip))
|
| 386 |
action_code = float(le_action.transform([action])[0])
|
| 387 |
|
|
|
|
| 718 |
outputs=[store_df_all, store_df_abn, abnormal_log_input]
|
| 719 |
)
|
| 720 |
|
| 721 |
+
# =====================================================================
|
| 722 |
+
# 👉 新增:隱藏的 UI 綁定區 (讓外部能透過 api_name 存取資料)
|
| 723 |
+
# =====================================================================
|
| 724 |
+
with gr.Group(visible=False):
|
| 725 |
+
api_stats_btn = gr.Button("API_Stats")
|
| 726 |
+
api_stats_out = gr.JSON()
|
| 727 |
+
api_stats_btn.click(fn=api_get_stats, inputs=[], outputs=[api_stats_out], api_name="get_stats")
|
| 728 |
+
|
| 729 |
+
api_logs_btn = gr.Button("API_Logs")
|
| 730 |
+
api_logs_in = gr.Number(value=20)
|
| 731 |
+
api_logs_out = gr.JSON()
|
| 732 |
+
api_logs_btn.click(fn=api_get_logs, inputs=[api_logs_in], outputs=[api_logs_out], api_name="get_logs")
|
| 733 |
+
|
| 734 |
+
api_chart_btn = gr.Button("API_Chart")
|
| 735 |
+
api_chart_in = gr.Textbox()
|
| 736 |
+
api_chart_out = gr.JSON()
|
| 737 |
+
api_chart_btn.click(fn=api_get_chart_data, inputs=[api_chart_in], outputs=[api_chart_out], api_name="get_chart_data")
|
| 738 |
+
# =====================================================================
|
| 739 |
+
|
| 740 |
if __name__ == "__main__":
|
| 741 |
demo.launch(css=custom_css)
|