Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/control-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ authenticated, CSRF-protected endpoint and node capability checks as an external
| --- | --- | --- |
| Navigation and context | Keeps the selected VotingPlugin node visible in the persistent header, groups existing tools in a desktop sidebar/mobile drawer, and provides bounded search shortcuts for pages, settings, and registered nodes | Search does not enumerate players; shortcut pages reuse the existing capability-gated panels; server changes keep the normal stale-result and unsaved-draft protections |
| Overview dashboard | Builds six summary cards, Attention Required, quick actions, topology, logged-service activity, and recent operation activity from the node registry plus existing `overview`, `vote-site-health`, and `vote-log-summary` inspections | Read-only aggregation; failed, incomplete, or malformed sub-inspections produce a warning rather than a Healthy claim; disconnected registered nodes produce a warning; unknown Minecraft presence is not treated as offline; VoteLog counts are labeled as logged events; actions only open existing safe workflows rather than inventing auto-fixes |
| Network Doctor | Runs `diagnostics` (which includes the overview fields), combines node health with Control's current topology, and displays checks for connector, configuration, Votifier, vote sites, rewards, logging, and proxy topology | Read-only; “healthy” is bounded reported state, not a synthetic vote |
| Setup Doctor | Runs `diagnostics` (which includes the overview fields), validates that response, combines node health with Control's current topology, and displays PASS/WARNING/FAIL/UNKNOWN checks with their evidence source for connector, configuration, Votifier, vote sites, rewards, logging, and proxy topology | Read-only; “healthy” is bounded reported state, not a synthetic vote, website probe, port test, or general database-health claim |
| Diagnostics download | Downloads the last Network Doctor result as local JSON | Redacted status bundle only; no raw configuration/logs/player records/infrastructure secrets |
| Activity | Loads the newest 50 live/recovered configuration operation views, labels phases, lineage, reload/rollback, resumes eligible guided preview approvals, and offers retry only when `retryable` | Recovered configuration history cannot be retried; approval is single-use and apply is CSRF-protected; proxy-method apply needs a new preview |
| Plugin update | Uploads one bounded JAR, shows the deployment-capable subset, and stages it on those nodes | SHA-256 and JAR identity are verified; session/attempt leases authorize downloads; Control never automatically reloads or restarts nodes; private storage is capped at 32 artifacts / 512 MiB and evicts only artifacts not referenced by retained deployment history |
Expand Down
3 changes: 2 additions & 1 deletion docs/mockup-capability-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ revision-aware `READ -> PREVIEW -> APPROVE -> APPLY -> CONFIRMED READ` workflow.

1. Improve the Full YAML workbench and continue splitting focused frontend modules. Add visible source/revision context,
line navigation/search, line numbers, and a reliable unsaved state without changing connector semantics.
2. Align Network Health and Setup Doctor navigation/layout with the approved cards and tables while retaining the current
2. **Implemented:** align Network Health and Setup Doctor navigation/layout with the approved cards and tables. The
Doctor renders PASS/WARNING/FAIL/UNKNOWN checks with evidence sources and safe navigation while retaining the current
observed/unknown evidence model.
3. Expand typed configuration in small groups sourced from current defaults and loaders. Prioritize reminders, Vote Party,
vote logging, proxy/storage, GUI, and Shop. Full YAML remains available for every managed file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public final class ControlHttpServer implements AutoCloseable {
Map.entry("/index.html", new WebResource("/web/index.html", "text/html; charset=utf-8")),
Map.entry("/app.js", new WebResource("/web/app.js", "text/javascript; charset=utf-8")),
Map.entry("/workspace.js", new WebResource("/web/workspace.js", "text/javascript; charset=utf-8")),
Map.entry("/network-doctor.js", new WebResource("/web/network-doctor.js", "text/javascript; charset=utf-8")),
Map.entry("/configuration-state.js", new WebResource("/web/configuration-state.js", "text/javascript; charset=utf-8")),
Map.entry("/yaml-editor.js", new WebResource("/web/yaml-editor.js", "text/javascript; charset=utf-8")),
Map.entry("/general-settings.js", new WebResource("/web/general-settings.js", "text/javascript; charset=utf-8")),
Expand Down
21 changes: 21 additions & 0 deletions src/main/resources/web/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ p { line-height: 1.55; }
.pill.warning { border-color: #785927; background: var(--amber-soft); color: var(--amber); }
.pill.neutral { border-color: var(--border-strong); background: #111e30; color: var(--muted); }

.doctor-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-bottom: 18px; }
.doctor-stat { display: flex; min-height: 82px; flex-direction: column; justify-content: center; padding: 14px 16px; border: 1px solid var(--border); border-radius: 11px; background: #091421; }
.doctor-stat strong { font-size: 1.5rem; }
.doctor-stat span { color: var(--muted); font-size: .72rem; font-weight: 800; letter-spacing: .08em; }
.doctor-stat.doctor-pass, .pill.doctor-pass { border-color: #2b7750; background: var(--green-soft); color: var(--green); }
.doctor-stat.doctor-warning, .pill.doctor-warning { border-color: #785927; background: var(--amber-soft); color: var(--amber); }
.doctor-stat.doctor-fail, .pill.doctor-fail { border-color: #82445a; background: #341521; color: var(--danger); }
.doctor-stat.doctor-unknown, .pill.doctor-unknown { border-color: var(--border-strong); background: #111e30; color: var(--muted); }
.doctor-groups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.doctor-group { min-width: 0; overflow: hidden; border: 1px solid var(--border); border-radius: 11px; background: #091421; }
.doctor-group h4 { padding: 13px 15px; border-bottom: 1px solid var(--border); }
.doctor-check { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 11px; align-items: start; padding: 14px 15px; border-bottom: 1px solid var(--border); }
.doctor-check:last-child { border-bottom: 0; }
.doctor-check p { margin: 4px 0; color: #d6e5f7; font-size: .84rem; line-height: 1.45; }
.doctor-check small { color: var(--muted); }
.doctor-check button { align-self: center; }

.topology { display: grid; gap: 11px; }
.topology.empty, .nodes.empty { display: block; padding: 30px; border: 1px dashed var(--border-strong); border-radius: 11px; color: var(--muted); text-align: center; }
.topology-row { display: grid; grid-template-columns: minmax(170px, .72fr) minmax(260px, 1.5fr); gap: 12px; align-items: stretch; }
Expand Down Expand Up @@ -305,6 +322,9 @@ li span { color: var(--muted); font-size: .9rem; }
.page-heading, .section-title, .auth-row, .editor-footer { align-items: stretch; flex-direction: column; }
.page-heading > button, .auth-row button { width: 100%; }
.metrics, .settings-grid, .feature-grid, .checklist { grid-template-columns: 1fr; }
.doctor-summary, .doctor-groups { grid-template-columns: 1fr 1fr; }
.doctor-check { grid-template-columns: auto minmax(0, 1fr); }
.doctor-check button { grid-column: 2; width: 100%; }
.metrics article { min-height: 92px; }
.subtabs button { padding-inline: 8px; }
.editor-toolbar { grid-template-columns: 1fr; }
Expand All @@ -324,6 +344,7 @@ li span { color: var(--muted); font-size: .9rem; }
.topbar { gap: 8px; }
.brand { display: none; }
.server-picker select { width: min(50vw, 180px); }
.doctor-summary, .doctor-groups { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
Expand Down
56 changes: 36 additions & 20 deletions src/main/resources/web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1780,8 +1780,8 @@ function renderScopeOverview() {
const aggregate = isWorkspaceOverview();
const source = nodeIndex.get(selectedServerId);
text(document.querySelector('#overview-title'), aggregate ? workspace.managementScope === 'GLOBAL'
? 'Network Overview' : `${workspace.selectedTargetIds.size} Servers — Workspace Overview`
: `${source?.displayName || 'Selected server'} — Server Overview`);
? 'Network Health' : `${workspace.selectedTargetIds.size} Servers — Health Overview`
: `${source?.displayName || 'Selected server'} — Server Health`);
text(document.querySelector('#overview-eyebrow'), aggregate ? 'Management workspace' : 'Individual server inspection');
document.querySelector('#individual-overview').hidden = aggregate;
voteActivity.closest('section').hidden = aggregate;
Expand Down Expand Up @@ -5307,9 +5307,9 @@ async function refreshDashboard() {
refreshSetupChecklist.addEventListener('click', async () => {
try {
const envelope = await runInspection('diagnostics', {}, setupChecklistStatus);
lastOverview = envelope.result;
lastOverview = normalizeDashboardOverview(envelope.result).result;
invalidateDashboardInspection();
updateSetupChecklist(envelope.result);
updateSetupChecklist(lastOverview);
} catch (error) { text(setupChecklistStatus, error.message); }
});
refreshDataOverview.addEventListener('click', () => refreshOverview(dataOverview));
Expand All @@ -5319,34 +5319,50 @@ runNetworkDoctor.addEventListener('click', async () => {
lastDiagnostics = null;
try {
const diagnostics = await runInspection('diagnostics', {}, networkDoctorResults);
lastOverview = diagnostics.result;
const normalizedDiagnostics = normalizeDashboardOverview(diagnostics.result);
lastOverview = normalizedDiagnostics.result;
invalidateDashboardInspection();
const node = nodeIndex.get(selectedServerId);
const voteLog = diagnostics.result.voteLoggingEnabled !== true
? {state: 'DISABLED', message: 'Vote logging is disabled; no retained logged-event history is expected.'}
: diagnostics.result.voteLogReadable === true
const report = ControlNetworkDoctor.buildReport({diagnostics: normalizedDiagnostics.result, node,
incomplete: normalizedDiagnostics.incomplete,
topologyComplete: !backendTopologyTruncated,
proxyReports: isBackend(node) ? proxyReportsFor(node.nodeId).length : 0});
Comment thread
BenCodez marked this conversation as resolved.
Resolved
const voteLogStatus = ControlNetworkDoctor.statusFor(report, 'VoteLog readability');
const voteLog = voteLogStatus === 'PASS'
? {state: 'READABLE', message: 'Retained logged-event history is readable. It is not a guaranteed record of every internal vote-delivery hop.'}
: {state: 'UNREADABLE', message: 'Vote logging is enabled, but retained logged-event history is not currently readable.'};
const configuredVoteSites = finiteCount(diagnostics.result.configuredVoteSites);
: voteLogStatus === 'FAIL'
? {state: 'UNREADABLE', message: 'Vote logging is enabled and available, but retained logged-event history is not readable.'}
: voteLogStatus === 'WARNING'
? {state: 'UNAVAILABLE', message: 'Vote logging is enabled, but its runtime adapter is unavailable. A backend restart may be required.'}
: {state: 'UNKNOWN', message: normalizedDiagnostics.result.voteLoggingEnabled === false
? 'Vote logging is disabled; no retained logged-event history is expected.'
: 'VoteLog state could not be classified from the available evidence.'};
const checks = {
controlConnected: Boolean(node?.online),
configurationHealthy: diagnostics.result.configurationHealthy,
votifierDetected: diagnostics.result.votifierDetected,
voteSitesConfigured: configuredVoteSites == null ? null : configuredVoteSites > 0,
voteSitesConfiguredKnown: configuredVoteSites != null,
processRewards: diagnostics.result.processRewards,
controlConnected: ControlNetworkDoctor.legacyBoolean(report, 'Control connector'),
configurationHealthy: ControlNetworkDoctor.legacyBoolean(report, 'Configuration state'),
votifierDetected: ControlNetworkDoctor.legacyBoolean(report, 'Votifier detected'),
voteSitesConfigured: ControlNetworkDoctor.statusFor(report, 'Vote Sites') === 'UNKNOWN'
? null : normalizedDiagnostics.result.configuredVoteSites > 0,
voteSitesConfiguredKnown: ControlNetworkDoctor.statusFor(report, 'Vote Sites') !== 'UNKNOWN',
processRewards: ControlNetworkDoctor.legacyBoolean(report, 'Reward processing'),
voteLogging: voteLog,
topologyReported: isBackend(node) ? proxyReportsFor(node.nodeId).length > 0 || !diagnostics.result.proxyMode : true
topologyReported: ControlNetworkDoctor.legacyBoolean(report, 'Proxy topology')
};
lastDiagnostics = {
schemaVersion: 1, generatedAt: new Date().toISOString(), selectedNodeId: selectedServerId,
checks, voteLog, node: diagnostics.result,
checks, voteLog, doctor: {counts: report.counts, checks: report.checks}, node: diagnostics.result,
Comment thread
BenCodez marked this conversation as resolved.
Resolved
control: {application: 'VotingPlugin Control', registeredNodes: allNodeItems.length,
nodes: allNodeItems.slice(0, 100).map(item => ({nodeId: item.nodeId, displayName: item.displayName,
role: roleLabel(item), online: item.online, pluginVersion: item.pluginVersion}))}
};
renderJsonResult(networkDoctorResults, lastDiagnostics);
updateSetupChecklist(diagnostics.result);
ControlNetworkDoctor.render(networkDoctorResults, report, action => {
if (action.serverScoped) {
if (!node || !changeWorkspaceTargets(() => workspace.setTargets([node.nodeId]))) return;
workspace.inspect(node.nodeId);
}
openWorkspace(action.tab, action.scrollTarget || '');
});
updateSetupChecklist(normalizedDiagnostics.result);
downloadNetworkDiagnostics.disabled = false;
} catch (error) { text(networkDoctorResults, error.message); }
});
Expand Down
11 changes: 6 additions & 5 deletions src/main/resources/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ <h2>Sign in to your network</h2>
<div class="page-heading">
<div>
<p class="eyebrow" id="overview-eyebrow">Workspace overview</p>
<h2 id="overview-title">Server Overview</h2>
<h2 id="overview-title">Network Health</h2>
</div>
<button id="refresh-dashboard" class="secondary" type="button">Refresh dashboard</button>
</div>
Expand Down Expand Up @@ -221,16 +221,16 @@ <h2>Servers</h2>
<section class="tab-panel" id="network-panel" data-panel="network" hidden>
<div class="page-heading">
<div>
<p class="eyebrow">Global VotingPlugin settings</p>
<h2>Network</h2>
<p>Manage network-wide VotingPlugin behavior and verify the live proxy-to-backend transport without sending a vote.</p>
<p class="eyebrow">Evidence-backed checks</p>
<h2>Setup Doctor &amp; Network</h2>
<p>Review observed VotingPlugin readiness, then manage network behavior or verify proxy-to-backend transport without sending a vote.</p>
</div>
</div>
<section class="card" id="network-doctor-card">
<div class="section-title">
<div>
<p class="eyebrow">Read-only checks</p>
<h3>Network Doctor</h3>
<h3>Setup Doctor</h3>
<p>Check Control connectivity, topology, configuration health, Votifier, vote sites, logging, and proxy mode without changing a server.</p>
</div>
<span class="pill neutral" id="network-doctor-capability">Choose an inspection-capable node</span>
Expand Down Expand Up @@ -741,6 +741,7 @@ <h3>Enrolled node IDs</h3>
<script src="/workspace.js" defer></script>
<script src="/configuration-state.js" defer></script>
<script src="/yaml-editor.js" defer></script>
<script src="/network-doctor.js" defer></script>
<script src="/general-settings.js" defer></script>
<script src="/vote-sites-state.js" defer></script>
<script src="/vote-sites.js" defer></script>
Expand Down
Loading