Conversation
stephdl
force-pushed
the
sdl-8099
branch
2 times, most recently
from
July 28, 2026 10:33
d9d1e6c to
0b7fcc7
Compare
Replace the top-right navigation button with two tabs, Certificates and ACME settings, on the TLS certificates page. The ACME servers page becomes a child component of that page and its route redirects to the new tab. Reorder the settings table columns so that the node comes first, and rename the section to "ACME settings". The certificates table is only re-indented, its content is unchanged.
Expose the ACME challenge type already supported by the traefik set-acme-server action. Administrators can now pick TLS-ALPN-01, needed when port 80 is unavailable or filtered, without editing traefik.yaml. The challenge type is displayed per node as a tag in the ACME settings table and is selectable in the edit modal through a required radio group. Options come from a single ACME_CHALLENGE_TYPES array, so adding DNS-01 later only means adding one entry and one translation key. Align the modal labels with the new ACME settings section.
The set-acme-server action resets the notification email whenever the email field is missing from the request, so every save from the edit modal silently cleared it. Send back the value returned by get-acme-server.
Bring the comments added with the ACME settings tab in line with the rest of core/ui, where 98% of comment blocks are a single line: keep the why, drop what the code already says and what only justified the change to a reviewer. Two of them are removed rather than shortened: the duplicate note about .bx--form-requirement, which is already explained where .challenge-error is defined, and the line restating the ACME_CHALLENGE_TYPES name.
set-acme-server restarts Traefik on the edited node, and Traefik carries the cluster admin websocket. The completion event that triggers the table refresh is therefore lost, the loading counters never return to zero, and the page stays on its skeleton rows until it is reloaded by hand. delete-certificate and set-certificate restart Traefik too, through cert_helpers.purge_acme_json_and_restart_traefik, so the Certificates tab has the same problem. Watch the Vuex isWebsocketConnected flag in both tabs: on reconnection, reset the counter and read the data again. Clamp the decrements so an event delivered after a reset cannot unbalance the counter, and key the ACME rows by Traefik instance so it cannot duplicate a row either. The edit modal clears its pending state on reconnection as a safety net. The modal keeps closing on validation-ok rather than on task creation: the ACME URL is validated server-side, so closing earlier would throw away the inline error for a malformed URL. Replace the leader-only reload warning with a restart warning shown for every node, since the restart happens on whichever node is edited.
7 tasks
…#1261) * fix(ui): count ACME and certificate reads upfront The counters were incremented inside the loop and decremented through a helper clamped with Math.max(0, n - 1). The clamp hides two things: the counter reaches zero between two iterations, so the table can leave its loading state while the next task is still being created, and a surplus decrement is silently swallowed instead of being impossible. Set the counter to the number of instances before the loop and decrement it plainly. clearTaskListeners() already makes a stale event unable to reach a handler, so nothing is left to clamp. Release listCertificatesNum when list-installed-modules is aborted: the certificates chain never runs, and the skeleton rows stayed for good when the abort followed a reconnection. * fix(ui): keep the ACME rows readable when one node fails error.getAcmeServer is a single shared string and NsDataTable renders its error notification instead of the rows, so one unreachable node hid the settings of every node that did answer. Show the notification above the table when there are rows to keep, and leave isErrorShown to the case where there is nothing else to display.
bx--grid caps its width at 99rem and centers itself, so the grid nested in the tile left empty gutters on screens wider than 1584px. Put the table directly in the tile, like the certificates tab does.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NethServer/dev#8099
Description
The TLS certificates page had a separate
ACME serverspage, reachable through a top-right navigation button. The ACME challenge type was already accepted by the traefikset-acme-serveraction but was not exposed anywhere in the UI, so administrators who needTLS-ALPN-01(typically when port 80 is unavailable or filtered) had to edittraefik.yamlby hand.This PR merges the two pages into one and exposes the challenge type.
CertificatesandACME settings.Node,ACME directory URL,Challenge type. The node comes first, which also fixes the previous wrong column order.HTTP-01andTLS-ALPN-01./settings/acme-serverspath redirects to/settings/tls-certificates?view=acme, so existing bookmarks and links keep working.ACME serverbecomesACME settings,Edit ACME serverbecomesEdit ACME settings.Settings remain per node, as before.
No backend change is needed:
set-acme-serveralready validateschallengeagainst the["TLS-ALPN-01", "HTTP-01"]enum, andget-acme-serveralready returns it.Extensibility
The options come from a single
ACME_CHALLENGE_TYPESarray exported byEditAcmeServerModal.vue, which drives the radio group, the tag colour and the labels. AddingDNS-01later means adding one entry and one translation key.DNS-01is intentionally left out of this iteration, as it needs extra configuration fields.Trusted proxy settings are not part of this PR: they will be exposed on the HTTP routes page and are tracked separately.
Robustness
An internal review pass raised a number of issues that are addressed in the same commits.
Tab selection is validated against a known list.
q.viewis normalised through aTAB_VIEWSconstant in both route guards. Previously only an empty value was defaulted, so?view=certificate(a stale or mistyped link) leftcv-tabswith no selected child — and becausenoDefaultToFirstis set, both panels kept theirhiddenattribute and the page body was empty. A same-path navigation whose query omitsviewnow also resets to the first tab instead of keeping whatever tab was open.The ACME tab mounts once and stays alive. It was gated by
v-if="q.view === 'acme'", which destroyed and recreated the child on every tab switch and re-issued onelist-installed-modulescluster task plus oneget-acme-servermodule task per node each time. A first-visit latch replaces it, so the chain runs once and the table's search, sort and page survive a switch.Task listeners are torn down.
AcmeSettings.vueandEditAcmeServerModal.vueregistered$root.$oncehandlers with nobeforeDestroy. Leaving the tab mid-flight ran a completion handler on a dead instance, which fanned out into one module task per node, and a save completing after the modal was gone silently lost its refresh. Both now record[eventName, handler]pairs and$offthem. This is safe becausesrc/mixins/notification.jsonly emits those names — all notification and drawer bookkeeping is store-driven and independent of listeners.The table error state clears. Nothing ever reset
error.getAcmeServer, so one temporarily offline node left the table showing the error banner instead of rows, even after every node answered again. AclearTableError()helper now runs at the start of each load. Thelist-installed-moduleserror branch also resets its loading flag, which it previously left stuck attrue.The challenge field error is visible. The message lived in a bare
.bx--form-requirementdiv, which Carbon keeps atdisplay: noneunless it follows a[data-invalid]field wrapper — something a radio group never is. A validation failure onchallengetherefore showed nothing at all. The div now carries a class that forces visibility and the error colour.Only reported fields are sent back. The payload is built incrementally and echoes back
challengeonly when the module reported one, andemailonly when it is defined. Since the action schema setsadditionalProperties: false, sending a key an older traefik does not know about would have failed the whole request. For the same reason the Challenge type block is hidden when the module reported no challenge type, so opening the modal to correct the URL can no longer silently switch that node toHTTP-01.Unknown validation parameters no longer throw.
setAcmeServerValidationFailedblindly assignedthis.error[param]and calledfocusElement(param). Withemailnow in the payload, a validation error on it would have written a non-reactive key and called.focus()on an undefined ref. Parameters without a field of their own are routed to the modal-level error notification instead.Surviving the Traefik restart
set-acme-serverends withsystemctl --user restart traefik.service, and Traefik is what serves the cluster admin UI and its websocket. So saving ACME settings drops the admin's own connection, and theset-acme-server-completedevent that carries the table refresh travels on that very socket. It is lost: the table kept showing the old URL and challenge type, and the loading counters never returned to zero, leaving skeleton rows until the page was reloaded by hand. That is what the earlierReload requiredwarning was working around.The Certificates tab has the same problem.
delete-certificateandset-certificatealso restart Traefik, throughcert_helpers.purge_acme_json_and_restart_traefik— the helper writes the unit astraefik, nottraefik.service, which is easy to miss when grepping.Both tabs now watch the Vuex
isWebsocketConnectedflag, whichApp.vuealready maintains and which no component on this page read: on reconnection they reset the counter and read their data again. Decrements are clamped withMath.max(0, n - 1)and the ACME rows are keyed by Traefik instance, so an event delivered after a reset can neither unbalance the counter nor duplicate a row. The edit modal clears its pending state on reconnection as a safety net. No manual reload is needed any more, so theReload requiredwarning is gone.The modal still closes on
validation-okrather than on task creation. The ACME URL is validated server-side against"format": "uri"while the client only checks non-empty, so closing earlier would throw away the inline error for a malformed URL.validation-okis emitted before the config is written, so it arrives ahead of the restart.The restart warning is no longer gated on the leader node. It was shown only when
server.nodeId == leaderNode.id, yet the restart happens on whichever node is edited — editing a worker restarted its Traefik with no warning at all. The warning is now shown for every node, names it, and says the page reconnects on its own. This matches how the rest of the page already works:DeleteObsoleteCertificatesModal.vueshows its restart warning unconditionally, and the delete-certificate warning is gated oncurrentCertificate.type === 'internal'— that is, on whether a restart actually happens, never on which node.This borrows the mechanism from #1258, which solved the same problem on the HTTP routes page. Since that PR is not merged, the pattern is currently duplicated on two branches; a shared mixin is the natural follow-up once both land.
Included fix
The edit modal used to send only
url. Since the action doesemail = request.get("email", ""), every save silently cleared the notification email. The modal now sends back the email returned byget-acme-server.Commits
The first commit only re-indents the certificates table to wrap it in a tab panel, no logic change. It is easier to read with whitespace hidden.
refactor(ui): merge ACME servers view into TLS certificates tabsfeat(ui): add ACME challenge type selectionfix(ui): preserve ACME email when saving settingsstyle(ui): shorten comments in the ACME settings UIfeat(ui): recover the TLS certificates page after a Traefik restartDesign
Mockups: Figma, design issue NethServer/dev#7973.
Testing
core/uihas no test runner configured, so this was checked with lint and a production build only, on the final tree and on each intermediate commit. All pass.Still to verify on a cluster:
?view=certificates/?view=acme)?view=acmelands on the ACME tab?view=certificatefalls back to the Certificates tab instead of an empty pageviewwhile the ACME tab is open lands on Certificates/settings/acme-serversredirects correctlyNodespage deep link (?selectedNodeId=...) still filters the certificates tableget-acme-serverburst afterwardstraefik.yamlapi-cli run module/traefik1/get-acme-serverTypeErrorget-acme-serveromitschallenge, the block is hidden, a URL-only save succeeds and the challenge type is not rewrittenRequiredmessageCertificatestab is unchangedThe restart handling needs the browser connected through the node being edited, since editing any other node does not drop your session:
The connected/disconnected toasts may churn for a while — that is the pre-existing
vue-native-websocketdouble-reconnect issue described in #1258, untouched here. What matters is that the page recovers its data without intervention.Follow-up
Out of scope here, worth separate issues:
views/Backup.vueandcomponents/domains/DomainUsersAndGroups.vueuse the sameq.view+noDefaultToFirstpattern and carry the same two tab-selection bugs.getAcmeServer()has no re-entrancy guard, so two closely spaced saves can still interleave. Rows can no longer be duplicated now that they are keyed by Traefik instance, and the clamped decrement keeps the counter sane, but a batch token would be cleaner.isWebsocketConnectedrecovery is duplicated between this page and the HTTP routes page of Add Frontend proxies tab to the HTTP routes page #1258. Once both are merged it belongs in a shared mixin.set-acme-serverandget-acme-serverare granted to no role inns8-traefik, so a delegatedcertadmuser probably cannot use this tab.get-acme-serverinfers the challenge type only fromhttpChallenge.entryPointand ignorestlsChallenge, so a hand-editedtraefik.yamlcan make the UI display the wrong tag.