Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bdaf0e6
Add network transport controls to hosted WebUI
BenCodez Aug 29, 2026
ba0f3c8
Address Codex transport control review findings
BenCodez Aug 29, 2026
8a7471a
Align transport stack with prerequisite fixes
BenCodez Aug 29, 2026
49ec9e9
Merge updated VoteSites prerequisite
BenCodez Aug 29, 2026
fbbf0b7
Merge updated VoteSites test prerequisite
BenCodez Aug 29, 2026
17c4b77
Address second Codex transport review
BenCodez Aug 29, 2026
86a9abe
Update transport topology regression assertion
BenCodez Aug 29, 2026
02d3227
Merge third VoteSites UI review fixes
BenCodez Aug 29, 2026
cf120d5
Revalidate proxy readiness before switching
BenCodez Aug 29, 2026
345140d
Refresh Control CI fixes
BenCodez Aug 29, 2026
da3a550
Lock proxy method workflow through apply
BenCodez Aug 29, 2026
789dc04
Refresh transport stack base
BenCodez Aug 29, 2026
1cf9571
Scope proxy topology completeness and roles
BenCodez Aug 29, 2026
da498d2
Update scoped topology regression assertion
BenCodez Aug 29, 2026
7da9762
Complete transport choices and final topology check
BenCodez Aug 29, 2026
9dea3cd
Rebuild diagnostics head with reviewed fixes
BenCodez Aug 29, 2026
1eeee94
Carry operation-boundary validation into diagnostics
BenCodez Aug 29, 2026
72182fa
Restore eligible primary targets after refresh
BenCodez Aug 29, 2026
b638849
Bound restored targets and mark stale presence
BenCodez Aug 29, 2026
f5ea6ab
Sequence proxy transport applies after backends
BenCodez Aug 29, 2026
096f760
Require and monitor complete proxy networks
BenCodez Aug 29, 2026
ea07b02
Count unregistered reported backends as issues
BenCodez Aug 29, 2026
2fbf377
Merge WebUI registry consistency fix
BenCodez Aug 30, 2026
8e88d3c
Preserve proxy method backend dependencies
BenCodez Aug 30, 2026
7d492ed
Merge registry scan CI fix
BenCodez Aug 30, 2026
98c75f5
Merge VoteSites sync fixes into transport diagnostics
BenCodez Aug 30, 2026
31899a3
Harden coordinated proxy method apply dependencies
BenCodez Aug 30, 2026
3cdb722
Serialize coordinated proxy method applies
BenCodez Aug 30, 2026
f67614f
Revalidate completed proxy method dependencies
BenCodez Aug 30, 2026
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public final class InMemoryNodeRegistry implements NodeRegistry {
public static final Set<String> SUPPORTED_CAPABILITIES = Set.of("discovery.read", "presence.snapshot",
ConfigurationOperations.CAPABILITY, ConfigurationOperations.FILE_CAPABILITY,
ConfigurationOperations.QUICK_SETUP_CAPABILITY, ConfigurationOperations.VOTE_SITES_SYNC_CAPABILITY,
ConfigurationOperations.TRANSPORT_TEST_CAPABILITY, ConfigurationOperations.PROXY_METHOD_CAPABILITY,
"config.file-comments.v1");
private static final Pattern ID = Pattern.compile("[A-Za-z0-9][A-Za-z0-9._-]{0,63}");
private static final Pattern CAPABILITY = Pattern.compile("[a-z][a-z0-9.-]{0,63}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ private void route(HttpExchange exchange) throws IOException {
send(exchange, 200, Map.of("items", page.items(), "offset", offset, "limit", limit,
"registryRevision", registryPage.revision(), "total", registryPage.total(),
"backendItemsReturned", page.backendItemsReturned(),
"backendItemsTruncatedNodeIds", page.backendItemsTruncatedNodeIds(),
"backendItemsTruncated", page.backendItemsTruncated()));
return;
}
Expand Down Expand Up @@ -554,22 +555,24 @@ private void handlePasswordLogin(HttpExchange exchange, PasswordRequest request,
}

static BackendPage boundedNodePage(List<NodeStatus> nodes) {
if (nodes.isEmpty()) return new BackendPage(List.of(), 0, false);
if (nodes.isEmpty()) return new BackendPage(List.of(), 0, false, List.of());
int perNode = Math.min(MAX_BACKENDS_PER_NODE_SUMMARY,
Math.max(1, MAX_BACKENDS_PER_NODE_PAGE / nodes.size()));
int returned = 0;
boolean truncated = false;
java.util.ArrayList<String> truncatedNodeIds = new java.util.ArrayList<>();
java.util.ArrayList<NodeStatus> items = new java.util.ArrayList<>(nodes.size());
for (NodeStatus node : nodes) {
int count = Math.min(perNode, node.backends().size());
returned += count;
truncated |= count < node.backends().size();
if (count < node.backends().size()) truncatedNodeIds.add(node.nodeId());
items.add(new NodeStatus(node.nodeId(), node.sessionId(), node.displayName(), node.platform(),
node.pluginVersion(), node.protocolVersion(), node.advertisedCapabilities(),
node.acceptedCapabilities(), node.detectedPlugins(), node.backends().subList(0, count),
node.snapshotSequence(), node.lastSeen(), node.lastAuthenticatedUpdate(), node.online()));
}
return new BackendPage(List.copyOf(items), returned, truncated);
return new BackendPage(List.copyOf(items), returned, truncated, List.copyOf(truncatedNodeIds));
}

private <T> T read(HttpExchange exchange, Class<T> type) throws IOException {
Expand Down Expand Up @@ -861,7 +864,8 @@ private record WebResource(String classpath, String contentType) { }
private record PasswordRequest(String password) { }
private record SetupRequest(String setupCode, String password) { }
private record EnrollmentRequest(String nodeId) { }
record BackendPage(List<NodeStatus> items, int backendItemsReturned, boolean backendItemsTruncated) { }
record BackendPage(List<NodeStatus> items, int backendItemsReturned, boolean backendItemsTruncated,
List<String> backendItemsTruncatedNodeIds) { }

static final class AuthFailureLimiter {
private final java.util.function.LongSupplier nanoTime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public record ManagedConfiguration(String domain, Boolean sendVotesToAllServers,
public static final String FILE = "file";
public static final String QUICK_SETUP = "quick-setup";
public static final String VOTE_SITES_SYNC = "sync-vote-sites";
public static final String COMMUNICATION_TEST = "communication-test";
public static final String PROXY_METHOD = "proxy-method";
public static final int MAX_CONTENT = 512 * 1024;

public ManagedConfiguration {
Expand Down Expand Up @@ -60,14 +62,26 @@ public void validateProposal() {
&& (options.size() != 1 || !options.containsKey("sourceContent"))) {
throw new IllegalArgumentException("VoteSites sync requires sourceContent");
}
if (QUICK_SETUP.equals(domain) && COMMUNICATION_TEST.equals(preset)
&& (options.size() != 1 || options.get("server") == null
|| !options.get("server").matches("[A-Za-z0-9][A-Za-z0-9._-]{0,63}"))) {
throw new IllegalArgumentException("communication test requires one valid server");
}
if (QUICK_SETUP.equals(domain) && PROXY_METHOD.equals(preset)
&& (options.size() != 1 || !List.of("PLUGINMESSAGING", "REDIS", "MQTT", "SOCKETS", "MYSQL")
.contains(options.get("method")))) {
throw new IllegalArgumentException("proxy method requires one supported method");
}
}

public String capability() {
return switch (domain) {
case PROXY_ROUTING -> "config.proxy-routing.v1";
case FILE -> "config.files.v1";
case QUICK_SETUP -> VOTE_SITES_SYNC.equals(preset)
? "config.vote-sites-sync.v1" : "config.quick-setup.v1";
? "config.vote-sites-sync.v1" : COMMUNICATION_TEST.equals(preset)
? "config.transport-test.v1" : PROXY_METHOD.equals(preset)
? "config.proxy-method.v1" : "config.quick-setup.v1";
Comment on lines 81 to +84

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate diagnostic preset payloads before queuing

When an authenticated API client submits proxy-method with a missing or unknown method, or communication-test without exactly one valid server, these new dedicated variants are still accepted because validateProposal() only constrains VoteSites sync and createRead() does not invoke it at all. The operation is then queued to capable nodes with a malformed protocol payload, producing delayed node-specific failures or implementation-dependent behavior instead of a deterministic 400 response; validate the exact option keys and allowed method values at the operation boundary.

Useful? React with 👍 / 👎.

default -> throw new IllegalStateException("unsupported configuration domain");
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/web/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ select { width: 100%; margin-bottom: 16px; }
.check-row { display: flex; align-items: center; gap: 10px; }
label span { color: var(--muted); font-weight: 400; }
.operation-actions { display: flex; flex-wrap: wrap; gap: 9px; }
#operation-status, #file-operation-status, #quick-operation-status, #vote-sites-sync-status { min-height: 84px; margin: 18px 0 0; padding: 14px; overflow: auto; border: 1px solid var(--border); border-radius: 9px; background: #050c16; color: #c6d5e8; white-space: pre-wrap; }
.method-buttons { display: grid; grid-template-columns: repeat(auto-fit, minmax(145px, 1fr)); gap: 9px; }
#operation-status, #file-operation-status, #quick-operation-status, #vote-sites-sync-status, #transport-test-status, #proxy-method-status { min-height: 84px; margin: 18px 0 0; padding: 14px; overflow: auto; border: 1px solid var(--border); border-radius: 9px; background: #050c16; color: #c6d5e8; white-space: pre-wrap; }
#file-operation-status { margin: 0; border: 0; border-top: 1px solid var(--border); border-radius: 0; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin: 16px 0; }
.form-grid label { color: var(--muted); }
Expand Down
Loading