Skip to content

Commit 5859ca5

Browse files
CopilotSteake
andcommitted
Improve input validation and error handling in admin console
Co-authored-by: Steake <530040+Steake@users.noreply.github.com>
1 parent 9881ea3 commit 5859ca5

3 files changed

Lines changed: 41 additions & 10 deletions

File tree

crates/bitcell-admin/src/api/nodes.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub struct StartNodeRequest {
3434

3535
/// Validate node ID format (alphanumeric, hyphens, and underscores only)
3636
fn validate_node_id(id: &str) -> Result<(), (StatusCode, Json<ErrorResponse>)> {
37-
if !id.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
37+
if id.is_empty() || !id.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
3838
return Err((
3939
StatusCode::BAD_REQUEST,
4040
Json(ErrorResponse {
@@ -83,6 +83,7 @@ pub async fn start_node(
8383

8484
// Config is not supported yet
8585
if req.config.is_some() {
86+
tracing::warn!("Node '{}': Rejected start request with unsupported config", id);
8687
return Err((
8788
StatusCode::BAD_REQUEST,
8889
Json(ErrorResponse {

crates/bitcell-admin/src/web/dashboard.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,8 +1047,8 @@ pub async fn index() -> impl IntoResponse {
10471047
const nodeType = document.getElementById('deploy-node-type').value;
10481048
const count = parseInt(document.getElementById('deploy-count').value);
10491049
1050-
if (count < 1 || count > 10) {
1051-
alert('Please enter a number between 1 and 10');
1050+
if (isNaN(count) || count < 1 || count > 10) {
1051+
alert('Please enter a valid number between 1 and 10');
10521052
return;
10531053
}
10541054
@@ -1063,8 +1063,18 @@ pub async fn index() -> impl IntoResponse {
10631063
});
10641064
10651065
if (!response.ok) {
1066-
const error = await response.text();
1067-
throw new Error(error);
1066+
let errorMessage = 'Deployment failed';
1067+
try {
1068+
const error = await response.json();
1069+
errorMessage = error.error || error.message || errorMessage;
1070+
} catch (e) {
1071+
const text = await response.text();
1072+
// Avoid showing large HTML blobs; use a generic message if text looks like HTML
1073+
if (text && !/^<!doctype|^<html/i.test(text.trim())) {
1074+
errorMessage = text;
1075+
}
1076+
}
1077+
throw new Error(errorMessage);
10681078
}
10691079
10701080
const data = await response.json();
@@ -1152,8 +1162,14 @@ pub async fn index() -> impl IntoResponse {
11521162
});
11531163
11541164
if (!response.ok) {
1155-
const error = await response.json();
1156-
throw new Error(error.error || 'Failed to start node');
1165+
let errorMessage = 'Failed to start node';
1166+
try {
1167+
const error = await response.json();
1168+
errorMessage = error.error || errorMessage;
1169+
} catch (e) {
1170+
// If JSON parsing fails, use default message
1171+
}
1172+
throw new Error(errorMessage);
11571173
}
11581174
11591175
updateNodes();
@@ -1168,8 +1184,14 @@ pub async fn index() -> impl IntoResponse {
11681184
const response = await fetch(`/api/nodes/${id}/stop`, { method: 'POST' });
11691185
11701186
if (!response.ok) {
1171-
const error = await response.json();
1172-
throw new Error(error.error || 'Failed to stop node');
1187+
let errorMessage = 'Failed to stop node';
1188+
try {
1189+
const error = await response.json();
1190+
errorMessage = error.error || errorMessage;
1191+
} catch (e) {
1192+
// If JSON parsing fails, use default message
1193+
}
1194+
throw new Error(errorMessage);
11731195
}
11741196
11751197
updateNodes();

crates/bitcell-ca/src/battle.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ impl Battle {
138138
/// # Performance Note
139139
/// This implementation sorts steps internally for incremental evolution efficiency,
140140
/// but returns grids in the original order requested.
141+
///
142+
/// # Memory Overhead
143+
/// Each grid clone can be expensive for large grids (e.g., 1024×1024 grids).
144+
/// Requesting many sample steps will require storing multiple grid copies in memory.
145+
/// For example, 100 sample steps could require several hundred MB of memory.
141146
pub fn grid_states(&self, sample_steps: &[usize]) -> Vec<Grid> {
142147
let initial = self.setup_grid();
143148

@@ -158,7 +163,10 @@ impl Battle {
158163

159164
for (original_idx, step) in &indexed_steps {
160165
let steps_to_evolve = step - prev_step;
161-
current_grid = evolve_n_steps(&current_grid, steps_to_evolve);
166+
// If steps_to_evolve is 0 (e.g., for step 0), the grid remains unchanged
167+
if steps_to_evolve > 0 {
168+
current_grid = evolve_n_steps(&current_grid, steps_to_evolve);
169+
}
162170
evolved_grids.push((*original_idx, current_grid.clone()));
163171
prev_step = *step;
164172
}

0 commit comments

Comments
 (0)