PhilipL commited on
Commit
d41d526
·
unverified ·
1 Parent(s): eb35977
Files changed (1) hide show
  1. app.py +93 -11
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)