Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,28 @@ def delete_screenshot(ip, port):
conn.close()


class TopologyModel:
"""Topology persistence model (JSON based)"""
TOPOLOGY_FILE = os.path.join(BASE_DIR, "database", "topology.json")

@staticmethod
def get_topology():
if not os.path.exists(TopologyModel.TOPOLOGY_FILE):
return {"nodes": [], "links": []}
try:
with open(TopologyModel.TOPOLOGY_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return {"nodes": [], "links": []}

@staticmethod
def save_topology(data):
os.makedirs(os.path.dirname(TopologyModel.TOPOLOGY_FILE), exist_ok=True)
with open(TopologyModel.TOPOLOGY_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return True


__all__ = [
'NmapModel',
'ScannerModel',
Expand All @@ -646,6 +668,7 @@ def delete_screenshot(ip, port):
'SwitchAclModel',
'SwitchWorkbenchModel',
'ScreenshotModel',
'TopologyModel',
'WorkflowModel',
'WorkflowRunModel',
'WorkflowWebhookModel',
Expand Down
262 changes: 262 additions & 0 deletions database/topology.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
{
"nodes": [
{
"id": "attacker-1",
"label": "境外攻击源 (Botnet)",
"type": "honeypot",
"status": "attack",
"index": 0,
"x": 428.57596855634057,
"y": 171.60980495967013,
"vy": -0.0018987867452464426,
"vx": 0.007685797513725976
},
{
"id": "attacker-2",
"label": "代理IP池",
"type": "honeypot",
"status": "warning",
"index": 1,
"x": 116.75040792306324,
"y": 425.1183725600246,
"vy": 0.012351837627149174,
"vx": -0.007589706389717562
},
{
"id": "internet",
"label": "互联网出口 (BGP)",
"type": "edge",
"status": "attack",
"index": 2,
"x": 300.22149442461665,
"y": -174.76713776930285,
"vy": -0.018367382475091648,
"vx": -0.0005844197126982671
},
{
"id": "fw-master",
"label": "主边界防火墙 (Active)",
"type": "firewall",
"status": "online",
"index": 3,
"x": 649.6295221715004,
"y": 474.21905366232653,
"vy": 0.013946950675394488,
"vx": 0.01797883531750017
},
{
"id": "waf-cluster",
"label": "WAF 集群 (拦截态)",
"type": "firewall",
"status": "warning",
"index": 4,
"x": -177.00737175061334,
"y": 171.29265972367295,
"vy": -0.00036708475055493444,
"vx": -0.022020664570525983
},
{
"id": "core-switch",
"label": "核心数据中心交换机",
"type": "switch",
"status": "online",
"index": 5,
"x": 782.9546123606096,
"y": -140.14917074466783,
"vy": -0.015079347138590195,
"vx": 0.02441690842764414
},
{
"id": "sw-dmz",
"label": "前置接入区(DMZ)交换机",
"type": "switch",
"status": "online",
"index": 6,
"x": 177.96177164038608,
"y": 807.4010857572364,
"vy": 0.03261955462286378,
"vx": -0.005039167990616037
},
{
"id": "sw-data",
"label": "后端数据区交换机",
"type": "switch",
"status": "online",
"index": 7,
"x": -77.00017532058098,
"y": -320.2769689341052,
"vy": -0.024649943539436725,
"vx": -0.017702918793203307
},
{
"id": "web-portal",
"label": "官网门户业务群",
"type": "server",
"status": "online",
"index": 8,
"x": 977.7492566637911,
"y": 610.1873552957246,
"vy": 0.019678203839574766,
"vx": 0.03467093457082847
},
{
"id": "api-gateway",
"label": "API 网关服务",
"type": "server",
"status": "online",
"index": 9,
"x": -522.93079139249,
"y": 358.47806849089255,
"vy": 0.008633461339625611,
"vx": -0.03867571316079293
},
{
"id": "auth-server",
"label": "认证统一鉴权服务",
"type": "server",
"status": "online",
"index": 10,
"x": 676.7240082221316,
"y": -522.336089215617,
"vy": -0.033288542808897086,
"vx": 0.01911265434919182
},
{
"id": "db-master",
"label": "主数据库 (MySQL)",
"type": "server",
"status": "online",
"index": 11,
"x": 542.7403594503015,
"y": 947.1334016245646,
"vy": 0.03733333179719581,
"vx": 0.014073908333029269
},
{
"id": "db-redis",
"label": "缓存集群 (Redis)",
"type": "server",
"status": "online",
"index": 12,
"x": -445.7939008379607,
"y": -221.2124761918933,
"vy": -0.01809302860212152,
"vx": -0.03551679858443929
},
{
"id": "hfish-node",
"label": "HFish 高交互蜜罐",
"type": "honeypot",
"status": "warning",
"index": 13,
"x": 1079.0547597926359,
"y": 85.01471842352132,
"vy": -0.0034691705761912338,
"vx": 0.038139932206445834
},
{
"id": "soc-center",
"label": "安全运营中心 (SOC)",
"type": "router",
"status": "online",
"index": 14,
"x": -231.27479693575472,
"y": 799.443855987421,
"vy": 0.029248152741041346,
"vx": -0.025104800962362183
},
{
"id": "node-1774266974111",
"label": "1",
"type": "server",
"status": "online",
"index": 15,
"x": 209.6456173097827,
"y": -631.1563465190238,
"vy": -0.038411095562109705,
"vx": -0.0031025027943211094
}
],
"links": [
{
"source": "attacker-1",
"target": "internet",
"type": "attack"
},
{
"source": "attacker-2",
"target": "internet",
"type": "attack"
},
{
"source": "internet",
"target": "fw-master",
"type": "uplink"
},
{
"source": "fw-master",
"target": "waf-cluster",
"type": "lan"
},
{
"source": "waf-cluster",
"target": "core-switch",
"type": "lan"
},
{
"source": "core-switch",
"target": "sw-dmz",
"type": "lan"
},
{
"source": "core-switch",
"target": "sw-data",
"type": "lan"
},
{
"source": "core-switch",
"target": "soc-center",
"type": "lan"
},
{
"source": "sw-dmz",
"target": "web-portal",
"type": "lan"
},
{
"source": "sw-dmz",
"target": "api-gateway",
"type": "lan"
},
{
"source": "sw-dmz",
"target": "auth-server",
"type": "lan"
},
{
"source": "sw-data",
"target": "db-master",
"type": "lan"
},
{
"source": "sw-data",
"target": "db-redis",
"type": "lan"
},
{
"source": "sw-dmz",
"target": "hfish-node",
"type": "lan"
},
{
"source": "fw-master",
"target": "hfish-node",
"type": "blocked"
},
{
"source": "waf-cluster",
"target": "attacker-1",
"type": "blocked"
}
]
}
34 changes: 34 additions & 0 deletions test_full_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import requests
import json

base_url = "http://127.0.0.1:5000"
login_url = f"{base_url}/api/v1/auth/login"
screen_url = f"{base_url}/api/v1/overview/screen"

data = {"username": "admin", "password": "admin123"}
headers = {"Content-Type": "application/json"}

try:
print("Testing Login...")
login_resp = requests.post(login_url, json=data, headers=headers)
print(f"Login Status: {login_resp.status_code}")
if login_resp.status_code == 200:
token = login_resp.json().get("access_token")
print("Login Success. Testing Overview Screen...")
auth_headers = {"Authorization": f"Bearer {token}"}
screen_resp = requests.get(screen_url, headers=auth_headers)
print(f"Screen Status: {screen_resp.status_code}")

print("Retrieving Backend Logs...")
logs_url = f"{base_url}/api/logs"
logs_resp = requests.get(logs_url)
print(f"Logs Status: {logs_resp.status_code}")
if logs_resp.status_code == 200:
logs = logs_resp.json()
for log in logs[-10:]:
print(f"[{log.get('time')}] [{log.get('level')}] {log.get('message')}")

else:
print(f"Login Failed: {login_resp.text}")
except Exception as e:
print(f"Error: {e}")
13 changes: 13 additions & 0 deletions test_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import requests
import json

url = "http://127.0.0.1:5000/api/v1/auth/login"
data = {"username": "admin", "password": "admin123"}
headers = {"Content-Type": "application/json"}

try:
response = requests.post(url, json=data, headers=headers)
print(f"Status Code: {response.status_code}")
print(f"Response Body: {response.text}")
except Exception as e:
print(f"Error: {e}")
2 changes: 2 additions & 0 deletions web/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .system import system_bp
from .nmap_routes import nmap_bp
from .switch_workbench import switch_workbench_bp
from .topology_routes import topology_bp
from .legacy import legacy_bp


Expand All @@ -25,6 +26,7 @@ def register_blueprints(app):
app.register_blueprint(system_bp)
app.register_blueprint(nmap_bp)
app.register_blueprint(switch_workbench_bp)
app.register_blueprint(topology_bp)
app.register_blueprint(legacy_bp)


Expand Down
23 changes: 23 additions & 0 deletions web/api/topology_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from flask import Blueprint, request
from database.models import TopologyModel
from .helpers import require_auth, ok, err

topology_bp = Blueprint('topology', __name__, url_prefix='/api/v1/topology')

@topology_bp.route('', methods=['GET'])
@require_auth
def get_topology():
"""Get the current topology layout"""
data = TopologyModel.get_topology()
return ok(data)

@topology_bp.route('', methods=['POST'])
@require_auth
def save_topology():
"""Save the updated topology layout"""
data = request.json
if not data or 'nodes' not in data:
return err("Invalid topology data")

TopologyModel.save_topology(data)
return ok({"message": "Topology saved successfully"})
6 changes: 6 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added web/vue/build_err.txt
Binary file not shown.
Binary file added web/vue/build_log.txt
Binary file not shown.
Binary file added web/vue/build_log_full.txt
Binary file not shown.
Loading