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
23 changes: 18 additions & 5 deletions src/main/resources/web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3467,6 +3467,12 @@ function operationSummary(operation) {
return lines.join('\n');
}

function presentPreviewReady(statusElement, applyButton, operation) {
const action = applyButton?.textContent?.trim() || 'Approve and apply';
text(statusElement, `${operationSummary(operation)}\nPREVIEW ONLY — nothing has been saved yet. Click “${action}” to write these exact changes.`);
if (applyButton && !applyButton.disabled) applyButton.focus({preventScroll: true});
}

function operationContext() {
return {authenticationGeneration, inputGeneration, selectedServerId,
selectedSessionId: nodeIndex.get(selectedServerId)?.sessionId};
Expand Down Expand Up @@ -3982,6 +3988,7 @@ previewConfiguration.addEventListener('click', async () => {
approvedPreview = {operationId: operation.operationId, approvalToken: operation.approvalToken,
nodeIds: targets('config.proxy-routing.v1')};
updateConfigurationButtons();
presentPreviewReady(operationStatus, applyConfiguration, operation);
} else if (previewGeneration !== inputGeneration) {
text(operationStatus, 'The targets or proposal changed while previewing. Preview again before apply.');
}
Expand Down Expand Up @@ -4123,10 +4130,10 @@ previewFileConfiguration.addEventListener('click', async () => {
&& previewTargets.every(nodeId => previewSessions.get(nodeId) === nodeIndex.get(nodeId)?.sessionId)
&& previewTargets.every(nodeId => fileTargetsForSelection(selectedFile).includes(nodeId));
if (operation.state === 'SUCCEEDED' && operation.approvalToken && previewGeneration === inputGeneration && targetsCurrent) {
text(fileOperationStatus, operationSummary(operation));
approvedFilePreview = {operationId: operation.operationId, approvalToken: operation.approvalToken,
nodeIds: previewTargets, fileName: selectedFile, sessions: previewSessions};
updateConfigurationButtons();
presentPreviewReady(fileOperationStatus, applyFileConfiguration, operation);
} else if (previewGeneration !== inputGeneration || !targetsCurrent) {
text(fileOperationStatus, 'The targets or file changed while previewing. Preview again before apply.');
} else {
Expand Down Expand Up @@ -4365,11 +4372,13 @@ previewQuickSetup.addEventListener('click', async () => {
nodeIds,
configuration: {domain: 'quick-setup', preset: 'sync-vote-sites', options: {sourceContent: source}}
}, quickOperationStatus);
text(quickOperationStatus, operationSummary(preview));
if (preview.state === 'SUCCEEDED' && preview.approvalToken && previewGeneration === inputGeneration) {
approvedQuickPreview = {workflow: 'sync-vote-sites', operationId: preview.operationId,
approvalToken: preview.approvalToken, nodeIds, sourceId};
updateConfigurationButtons();
presentPreviewReady(quickOperationStatus, applyQuickSetup, preview);
} else {
text(quickOperationStatus, operationSummary(preview));
}
return;
}
Expand All @@ -4378,11 +4387,11 @@ previewQuickSetup.addEventListener('click', async () => {
nodeIds,
configuration: {domain: 'quick-setup', preset: quickPreset.value, options: quickOptions()}
}, quickOperationStatus);
text(quickOperationStatus, operationSummary(operation));
if (operation.state === 'SUCCEEDED' && operation.approvalToken && previewGeneration === inputGeneration) {
approvedQuickPreview = {operationId: operation.operationId, approvalToken: operation.approvalToken,
nodeIds};
updateConfigurationButtons();
presentPreviewReady(quickOperationStatus, applyQuickSetup, operation);
} else if (previewGeneration !== inputGeneration) {
text(quickOperationStatus, 'The targets or setup changed while previewing. Preview again before apply.');
}
Expand Down Expand Up @@ -4573,8 +4582,8 @@ function validatedPurgeDays(field) {

function dedicatedSetupElements(preset) {
return preset === 'auto-create-vote-sites'
? {status: autoSitesStatus, state: autoSitesState, retry: loadAutoSites}
: {status: voteLoggingStatus, state: voteLoggingState, retry: loadVoteLogging};
? {status: autoSitesStatus, state: autoSitesState, retry: loadAutoSites, apply: applyAutoSites}
: {status: voteLoggingStatus, state: voteLoggingState, retry: loadVoteLogging, apply: applyVoteLogging};
}

async function loadDedicatedSetup(preset, automatic = false) {
Expand Down Expand Up @@ -4635,6 +4644,8 @@ async function previewDedicatedSetup(preset) {
} else if (operation.state === 'SUCCEEDED' && operation.approvalToken) {
dedicatedSetupApprovals.set(preset, {operationId: operation.operationId,
approvalToken: operation.approvalToken, nodeIds});
updateExtendedButtons();
presentPreviewReady(elements.status, elements.apply, operation);
}
} catch (error) { text(elements.status, error.message); }
updateExtendedButtons();
Expand Down Expand Up @@ -5169,6 +5180,8 @@ previewReward.addEventListener('click', async () => {
} else if (operation.state === 'SUCCEEDED' && operation.approvalToken) {
dedicatedSetupApprovals.set('reward-builder', {operationId: operation.operationId,
approvalToken: operation.approvalToken, nodeIds});
updateExtendedButtons();
presentPreviewReady(rewardSimulationResult, applyReward, operation);
}
} catch (error) { text(rewardSimulationResult, error.message); }
updateExtendedButtons();
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ <h3>Full VotingPlugin configuration</h3>
<label class="sr-only" for="configuration-content">YAML content</label>
<textarea id="configuration-content" class="yaml-editor" rows="26" spellcheck="false" placeholder="# Read the selected file to begin editing"></textarea>
<div class="editor-footer">
<span><output id="editor-position">Line 1, Column 1</output> · Tab inserts two spaces · Ctrl/⌘+S previews</span>
<span><output id="editor-position">Line 1, Column 1</output> · Tab inserts two spaces · Ctrl/⌘+S previews only; use Approve and apply to save</span>
<div class="operation-actions">
<button id="read-file-configuration" class="secondary" type="button" disabled hidden>Retry read</button>
<button id="preview-file-configuration" type="button" disabled>Preview file</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,12 @@ class ControlHttpServerTest {
assertTrue(script.body().contains("readConfiguration.disabled = !routingReadReady;"));
assertTrue(script.body().contains("previewConfiguration.disabled = !routingDraftReady;"));
assertTrue(script.body().contains("applyConfiguration.disabled = !routingDraftReady || !approvedPreview;"));
assertTrue(script.body().contains("PREVIEW ONLY — nothing has been saved yet."));
assertTrue(script.body().contains("presentPreviewReady(operationStatus, applyConfiguration, operation);"));
assertTrue(script.body().contains("presentPreviewReady(fileOperationStatus, applyFileConfiguration, operation);"));
assertTrue(script.body().contains("presentPreviewReady(quickOperationStatus, applyQuickSetup, operation);"));
assertTrue(script.body().contains("presentPreviewReady(elements.status, elements.apply, operation);"));
assertTrue(script.body().contains("presentPreviewReady(rewardSimulationResult, applyReward, operation);"));
assertTrue(script.body().contains("Your unsaved proxy-routing draft is retained"));
assertTrue(script.body().contains("function invalidateConfigurationReads() {\n fileReadCache.clear();\n"
+ " lastFileReadOperation = null;\n clearApprovals();\n loadedQuickSetup = null;\n"
Expand Down
Loading