Skip to content

Commit 9f38075

Browse files
improve federation compatibility with the frontend
1 parent 5a38e51 commit 9f38075

File tree

7 files changed

+669
-102
lines changed

7 files changed

+669
-102
lines changed

nebula/addons/reporter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ async def report_scenario_finished(self):
163163
"Content-Type": "application/json",
164164
"User-Agent": f"NEBULA Participant {self.config.participant['device_args']['idx']}",
165165
}
166+
try:
167+
await self.__report_status_to_controller()
168+
except Exception as e:
169+
logging.exception(f"Error reporting status before scenario finished: {e}")
166170
try:
167171
async with aiohttp.ClientSession() as session, session.post(url, data=data, headers=headers) as response:
168172
if response.status != 200:

nebula/frontend/app.py

Lines changed: 30 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -641,74 +641,55 @@ async def nebula_dashboard_monitor(scenario_name: str, request: Request, session
641641
if scenario:
642642
nodes_list = list_nodes_by_scenario_name(scenario_name)
643643
if nodes_list:
644-
nodes_config = []
645-
nodes_status = []
644+
formatted_nodes = []
646645
for node in nodes_list:
647-
nodes_config.append((node[2], node[3], node[4])) # IP, Port, Role
648-
if datetime.datetime.now() - datetime.datetime.strptime(
649-
node[8], "%Y-%m-%d %H:%M:%S.%f"
650-
) > datetime.timedelta(seconds=25):
651-
nodes_status.append(False)
652-
else:
653-
nodes_status.append(True)
654-
nodes_table = zip(
655-
[x[0] for x in nodes_list], # UID
656-
[x[1] for x in nodes_list], # IDX
657-
[x[2] for x in nodes_list], # IP
658-
[x[3] for x in nodes_list], # Port
659-
[x[4] for x in nodes_list], # Role
660-
[x[5] for x in nodes_list], # Neighbors
661-
[x[6] for x in nodes_list], # Latitude
662-
[x[7] for x in nodes_list], # Longitude
663-
[x[8] for x in nodes_list], # Timestamp
664-
[x[9] for x in nodes_list], # Federation
665-
[x[10] for x in nodes_list], # Round
666-
[x[11] for x in nodes_list], # Scenario name
667-
[x[12] for x in nodes_list], # Run hash
668-
[x[13] for x in nodes_list], # Malicious
669-
nodes_status,
670-
strict=False, # Status
671-
)
672-
673-
topology_path = FileUtils.check_path(settings.config_dir, os.path.join(scenario_name, "topology.png"))
674-
if os.path.exists(topology_path):
675-
latest_participant_file_mtime = max([
676-
os.path.getmtime(
677-
os.path.join(
678-
settings.config_dir,
679-
scenario_name,
680-
f"participant_{node[1]}.json",
681-
)
682-
)
683-
for node in nodes_list
684-
])
685-
if os.path.getmtime(topology_path) < latest_participant_file_mtime:
686-
update_topology(scenario[0], nodes_list, nodes_config)
687-
else:
688-
update_topology(scenario[0], nodes_list, nodes_config)
646+
# Calculate initial status based on timestamp
647+
timestamp = datetime.datetime.strptime(node[8], "%Y-%m-%d %H:%M:%S.%f")
648+
is_online = (datetime.datetime.now() - timestamp) <= datetime.timedelta(seconds=25)
649+
650+
formatted_nodes.append({
651+
"uid": node[0],
652+
"idx": node[1],
653+
"ip": node[2],
654+
"port": node[3],
655+
"role": node[4],
656+
"neighbors": node[5],
657+
"latitude": node[6],
658+
"longitude": node[7],
659+
"timestamp": node[8],
660+
"federation": node[9],
661+
"round": str(node[10]),
662+
"scenario_name": node[11],
663+
"hash": node[12],
664+
"malicious": node[13],
665+
"status": is_online
666+
})
689667

668+
# For HTML response, return the template with basic data
690669
if request.url.path == f"/platform/dashboard/{scenario_name}/monitor":
691670
return templates.TemplateResponse(
692671
"monitor.html",
693672
{
694673
"request": request,
695674
"scenario_name": scenario_name,
696675
"scenario": scenario,
697-
"nodes": nodes_table,
676+
"nodes": [list(node.values()) for node in formatted_nodes],
698677
"user_logged_in": session.get("user"),
699678
},
700679
)
680+
# For API response, return the formatted node data
701681
elif request.url.path == f"/platform/api/dashboard/{scenario_name}/monitor":
702682
return JSONResponse({
703683
"scenario_status": scenario[5],
704-
"nodes_table": list(nodes_table),
684+
"nodes": formatted_nodes,
705685
"scenario_name": scenario[0],
706686
"scenario_title": scenario[3],
707687
"scenario_description": scenario[4],
708688
})
709689
else:
710690
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
711691
else:
692+
# No nodes found
712693
if request.url.path == f"/platform/dashboard/{scenario_name}/monitor":
713694
return templates.TemplateResponse(
714695
"monitor.html",
@@ -723,14 +704,15 @@ async def nebula_dashboard_monitor(scenario_name: str, request: Request, session
723704
elif request.url.path == f"/platform/api/dashboard/{scenario_name}/monitor":
724705
return JSONResponse({
725706
"scenario_status": scenario[5],
726-
"nodes_table": [],
707+
"nodes": [],
727708
"scenario_name": scenario[0],
728709
"scenario_title": scenario[3],
729710
"scenario_description": scenario[4],
730711
})
731712
else:
732713
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
733714
else:
715+
# Scenario not found
734716
if request.url.path == f"/platform/dashboard/{scenario_name}/monitor":
735717
return templates.TemplateResponse(
736718
"monitor.html",
@@ -816,6 +798,7 @@ async def nebula_update_node(scenario_name: str, request: Request):
816798
"name": config["scenario_args"]["name"],
817799
"status": True,
818800
"neighbors_distance": neighbors_distance,
801+
"malicious": str(config["device_args"]["malicious"])
819802
}
820803

821804
try:

nebula/frontend/database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,9 @@ async def update_node_record(
337337
federation,
338338
federation_round,
339339
run_hash,
340+
malicious,
340341
node_uid,
341342
scenario,
342-
malicious,
343343
),
344344
)
345345

nebula/frontend/static/js/deployment/attack.js

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -107,47 +107,50 @@ const AttackManager = (function() {
107107
const config = {
108108
type: attackType,
109109
poisonedNodePercent: parseFloat(document.getElementById("poisoned-node-percent").value),
110-
startRound: parseInt(document.getElementById("start-attack").value),
111-
stopRound: parseInt(document.getElementById("stop-attack").value),
112-
attackInterval: parseInt(document.getElementById("attack-interval").value)
110+
round_start_attack: parseInt(document.getElementById("start-attack").value),
111+
round_stop_attack: parseInt(document.getElementById("stop-attack").value),
112+
attack_interval: parseInt(document.getElementById("attack-interval").value)
113113
};
114114

115115
switch(attackType) {
116116
case ATTACK_TYPES.LABEL_FLIPPING:
117-
config.poisonedSamplePercent = parseFloat(document.getElementById("poisoned-sample-percent").value);
117+
config.poisoned_percent = parseFloat(document.getElementById("poisoned-sample-percent").value);
118118
config.targeted = document.getElementById("targeted").checked;
119119
if(config.targeted) {
120-
config.targetLabel = parseInt(document.getElementById("target_label").value);
121-
config.targetChangedLabel = parseInt(document.getElementById("target_changed_label").value);
120+
config.target_label = parseInt(document.getElementById("target_label").value);
121+
config.target_changed_label = parseInt(document.getElementById("target_changed_label").value);
122122
}
123123
break;
124124

125125
case ATTACK_TYPES.SAMPLE_POISONING:
126-
config.poisonedSamplePercent = parseFloat(document.getElementById("poisoned-sample-percent").value);
127-
config.poisonedNoisePercent = parseFloat(document.getElementById("poisoned-noise-percent").value);
128-
config.noiseType = document.getElementById("noise_type").value;
126+
config.poisoned_percent = parseFloat(document.getElementById("poisoned-sample-percent").value);
127+
config.poisoned_ratio = parseFloat(document.getElementById("poisoned-noise-percent").value);
128+
config.noise_type = document.getElementById("noise_type").value;
129129
config.targeted = document.getElementById("targeted").checked;
130+
if(config.targeted) {
131+
config.target_label = parseInt(document.getElementById("target_label").value);
132+
}
130133
break;
131134

132135
case ATTACK_TYPES.MODEL_POISONING:
133-
config.poisonedNoisePercent = parseFloat(document.getElementById("poisoned-noise-percent").value);
134-
config.noiseType = document.getElementById("noise_type").value;
136+
config.poisoned_ratio = parseFloat(document.getElementById("poisoned-noise-percent").value);
137+
config.noise_type = document.getElementById("noise_type").value;
135138
break;
136139

137140
case ATTACK_TYPES.SWAPPING_WEIGHTS:
138-
config.layerIdx = parseInt(document.getElementById("layer_idx").value);
141+
config.layer_idx = parseInt(document.getElementById("layer_idx").value);
139142
break;
140143

141144
case ATTACK_TYPES.DELAYER:
142145
config.delay = parseInt(document.getElementById("delay").value);
143-
config.targetPercentage = parseFloat(document.getElementById("target-percentage").value);
144-
config.selectionInterval = parseInt(document.getElementById("selection-interval").value);
146+
config.target_percentage = parseInt(document.getElementById("target-percentage").value);
147+
config.selection_interval = parseInt(document.getElementById("selection-interval").value);
145148
break;
146149

147150
case ATTACK_TYPES.FLOODING:
148-
config.floodingFactor = parseInt(document.getElementById("flooding-factor").value);
149-
config.targetPercentage = parseFloat(document.getElementById("target-percentage").value);
150-
config.selectionInterval = parseInt(document.getElementById("selection-interval").value);
151+
config.flooding_factor = parseInt(document.getElementById("flooding-factor").value);
152+
config.target_percentage = parseInt(document.getElementById("target-percentage").value);
153+
config.selection_interval = parseInt(document.getElementById("selection-interval").value);
151154
break;
152155
}
153156

@@ -163,47 +166,50 @@ const AttackManager = (function() {
163166

164167
// Set common fields
165168
document.getElementById("poisoned-node-percent").value = config.poisonedNodePercent || 0;
166-
document.getElementById("start-attack").value = config.startRound || 1;
167-
document.getElementById("stop-attack").value = config.stopRound || 10;
168-
document.getElementById("attack-interval").value = config.attackInterval || 1;
169+
document.getElementById("start-attack").value = config.round_start_attack || 1;
170+
document.getElementById("stop-attack").value = config.round_stop_attack || 10;
171+
document.getElementById("attack-interval").value = config.attack_interval || 1;
169172

170173
// Set attack-specific fields
171174
switch(config.type) {
172175
case ATTACK_TYPES.LABEL_FLIPPING:
173-
document.getElementById("poisoned-sample-percent").value = config.poisonedSamplePercent || 0;
176+
document.getElementById("poisoned-sample-percent").value = config.poisoned_percent || 0;
174177
document.getElementById("targeted").checked = config.targeted || false;
175178
if(config.targeted) {
176-
document.getElementById("target_label").value = config.targetLabel || 4;
177-
document.getElementById("target_changed_label").value = config.targetChangedLabel || 7;
179+
document.getElementById("target_label").value = config.target_label || 4;
180+
document.getElementById("target_changed_label").value = config.target_changed_label || 7;
178181
}
179182
break;
180183

181184
case ATTACK_TYPES.SAMPLE_POISONING:
182-
document.getElementById("poisoned-sample-percent").value = config.poisonedSamplePercent || 0;
183-
document.getElementById("poisoned-noise-percent").value = config.poisonedNoisePercent || 0;
184-
document.getElementById("noise_type").value = config.noiseType || "Salt";
185+
document.getElementById("poisoned-sample-percent").value = config.poisoned_percent || 0;
186+
document.getElementById("poisoned-noise-percent").value = config.poisoned_ratio || 0;
187+
document.getElementById("noise_type").value = config.noise_type || "Salt";
185188
document.getElementById("targeted").checked = config.targeted || false;
189+
if(config.targeted) {
190+
document.getElementById("target_label").value = config.target_label || 4;
191+
}
186192
break;
187193

188194
case ATTACK_TYPES.MODEL_POISONING:
189-
document.getElementById("poisoned-noise-percent").value = config.poisonedNoisePercent || 0;
190-
document.getElementById("noise_type").value = config.noiseType || "Salt";
195+
document.getElementById("poisoned-noise-percent").value = config.poisoned_ratio || 0;
196+
document.getElementById("noise_type").value = config.noise_type || "Salt";
191197
break;
192198

193199
case ATTACK_TYPES.SWAPPING_WEIGHTS:
194-
document.getElementById("layer_idx").value = config.layerIdx || 0;
200+
document.getElementById("layer_idx").value = config.layer_idx || 0;
195201
break;
196202

197203
case ATTACK_TYPES.DELAYER:
198204
document.getElementById("delay").value = config.delay || 10;
199-
document.getElementById("target-percentage").value = config.targetPercentage || 100;
200-
document.getElementById("selection-interval").value = config.selectionInterval || 1;
205+
document.getElementById("target-percentage").value = config.target_percentage || 100;
206+
document.getElementById("selection-interval").value = config.selection_interval || 1;
201207
break;
202208

203209
case ATTACK_TYPES.FLOODING:
204-
document.getElementById("flooding-factor").value = config.floodingFactor || 100;
205-
document.getElementById("target-percentage").value = config.targetPercentage || 100;
206-
document.getElementById("selection-interval").value = config.selectionInterval || 1;
210+
document.getElementById("flooding-factor").value = config.flooding_factor || 100;
211+
document.getElementById("target-percentage").value = config.target_percentage || 100;
212+
document.getElementById("selection-interval").value = config.selection_interval || 1;
207213
break;
208214
}
209215
}

nebula/frontend/static/js/deployment/scenario.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ const ScenarioManager = (function() {
5454
// Get topology type from select element
5555
const topologyType = document.getElementById('predefined-topology-select').value;
5656

57+
// Get attack configuration
58+
const attackConfig = window.AttackManager.getAttackConfig();
59+
60+
// Map frontend parameter names to backend expected names
61+
const attackParams = {
62+
round_start_attack: attackConfig.round_start_attack || 1,
63+
round_stop_attack: attackConfig.round_stop_attack || 10,
64+
attack_interval: attackConfig.attack_interval || 1,
65+
poisoned_percent: attackConfig.poisonedSamplePercent || 0,
66+
poisoned_ratio: attackConfig.poisonedNoisePercent || 0,
67+
noise_type: attackConfig.noiseType || "Salt",
68+
targeted: attackConfig.targeted || false,
69+
target_label: attackConfig.targetLabel || 4,
70+
target_changed_label: attackConfig.targetChangedLabel || 7,
71+
layer_idx: attackConfig.layerIdx || 0,
72+
delay: attackConfig.delay || 10,
73+
target_percentage: attackConfig.targetPercentage || 100,
74+
selection_interval: attackConfig.selectionInterval || 1,
75+
flooding_factor: attackConfig.floodingFactor || 100
76+
};
77+
5778
return {
5879
scenario_title: document.getElementById("scenario-title").value,
5980
scenario_description: document.getElementById("scenario-description").value,
@@ -74,11 +95,11 @@ const ScenarioManager = (function() {
7495
logginglevel: document.getElementById("loggingLevel").value === "true",
7596
report_status_data_queue: document.getElementById("reportingSwitch").checked,
7697
epochs: parseInt(document.getElementById("epochs").value),
77-
attacks: window.AttackManager.getAttackConfig().attacks || [],
78-
poisoned_node_percent: window.AttackManager.getAttackConfig().poisoned_node_percent || 0,
79-
poisoned_sample_percent: window.AttackManager.getAttackConfig().poisoned_sample_percent || 0,
80-
poisoned_noise_percent: window.AttackManager.getAttackConfig().poisoned_noise_percent || 0,
81-
attack_params: window.AttackManager.getAttackConfig().attack_params || {},
98+
attacks: [attackConfig.type],
99+
poisoned_node_percent: attackConfig.poisonedNodePercent || 0,
100+
poisoned_sample_percent: attackConfig.poisonedSamplePercent || 0,
101+
poisoned_noise_percent: attackConfig.poisonedNoisePercent || 0,
102+
attack_params: attackParams,
82103
with_reputation: window.ReputationManager.getReputationConfig().with_reputation || false,
83104
reputation_metrics: window.ReputationManager.getReputationConfig().reputation_metrics || [],
84105
initial_reputation: window.ReputationManager.getReputationConfig().initial_reputation || 1.0,

0 commit comments

Comments
 (0)