Skip to content

TLS certificates page: ACME settings tab with challenge type selection - #1257

Open
stephdl wants to merge 13 commits into
mainfrom
sdl-8099
Open

TLS certificates page: ACME settings tab with challenge type selection#1257
stephdl wants to merge 13 commits into
mainfrom
sdl-8099

Conversation

@stephdl

@stephdl stephdl commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

The TLS certificates page had a separate ACME servers page reachable through a top-right button, and the ACME challenge type — already accepted by set-acme-server — was exposed nowhere, so TLS-ALPN-01 (needed when port 80 is unavailable) required editing traefik.yaml by hand.

This PR merges the two pages into two tabs, Certificates and ACME settings, and exposes the challenge type as a tag per node plus a required radio group in the edit modal. Options come from a single ACME_CHALLENGE_TYPES array, so DNS-01 is one entry plus one translation key away; it is left out here because it needs extra fields. The ACME table columns become Node, ACME directory URL, Challenge type, which also fixes the previous wrong order, and /settings/acme-servers redirects to /settings/tls-certificates?view=acme.

Settings remain per node. No backend change: set-acme-server already validates challenge and get-acme-server already returns it.

Surviving the Traefik restart

set-acme-server restarts Traefik, which serves the admin UI and its websocket, so the completion event carrying the table refresh is lost: stale values and skeleton rows until a manual reload. The Certificates tab has the same problem through cert_helpers.purge_acme_json_and_restart_traefik. Both tabs now watch the Vuex isWebsocketConnected flag and reload on reconnection; counters are clamped and ACME rows keyed by Traefik instance, so a late event cannot unbalance a counter or duplicate a row. The Reload required warning is gone, and the restart warning is no longer gated on the leader node — the restart happens on whichever node is edited. Mechanism borrowed from #1258, which fixes the same thing on HTTP routes; a shared mixin is the follow-up once both land.

Design

Mockups: Figma, design issue NethServer/dev#7973.

Refs: NethServer/dev#8099

@stephdl
stephdl requested review from andre8244 and removed request for AmaLuci August 6, 2026 15:05
Comment thread core/ui/public/i18n/en/translation.json Outdated
"no_acme_server_description": "There is no ACME server configured",
"url": "URL",
"acme_settings": "ACME settings",
"search_acme_settings": "Search ACME settings",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
"search_acme_settings": "Search ACME settings",
"search_acme_settings": "Search",

Comment thread core/ui/public/i18n/en/translation.json Outdated
"challenge_tls_alpn_01": "TLS-ALPN-01",
"edit_acme_settings": "Edit ACME settings",
"acme_settings_for_node": "ACME settings for {node}",
"acme_restart_message": "HTTP clients on {node} will be briefly disconnected. Reload the page if necessary.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
"acme_restart_message": "HTTP clients on {node} will be briefly disconnected. Reload the page if necessary.",
"acme_restart_message": "HTTP clients on {node} will be briefly disconnected. The page should reload automatically — otherwise, reload it manually..",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied, minus the double period at the end.

One thing to check: the app never reloads by itself. When Traefik restarts on the leader, the websocket drops and App.vue shows a persistent warning toast with a "Reload" action, then retries the connection after 10 seconds. So the page recovers on its own, but it does not reload. Is "reload automatically" still the wording you want, or would "reconnect automatically" be closer?

@andre8244 andre8244 Aug 26, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, but IIRC the websocket warning toast disappears as soon as the connection is resumed, right?

Anyway, for the message I propose "HTTP clients on {node} will be briefly disconnected. The connection should resume automatically — otherwise, reload the page manually."

@stephdl stephdl Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, but IIRC the websocket warning toast disappears as soon as the connection is resumed, right?

Yes and once reconnected we trigger all the list-/get- function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, applied your wording.

/>
</cv-data-table-cell>
<cv-data-table-cell>
<div class="justify-flex-end">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can use class="flex justify-end", these class already exist here https://github.com/NethServer/ns8-core/blob/main/core/ui/src/styles/_utils.scss

<style scoped lang="scss">
@import "../../styles/carbon-utils";

.justify-flex-end {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can remove this class


// carbon renders the interactive tooltip trigger as a button with a default
// cursor, which hides the fact the tooltip opens on click
.clickable-tooltip ::v-deep .bx--tooltip__trigger,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are these CSS rules needed? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Half of them were, yes. I measured it in the browser: carbon already sets cursor: pointer via .bx--tooltip__trigger:not(.bx--btn--icon-only), so the rule on the trigger did nothing. I removed it.

But .bx--tag sets cursor: default, so the pointer was lost when hovering the "Obsolete" tag itself, which is the trigger content. I kept one narrow rule for that case:

// carbon's .bx--tag forces a default cursor, hiding that the tooltip opens on click
.clickable-tag ::v-deep .bx--tag {
  cursor: pointer;
}

Happy to drop it too if you would rather not special-case the tag.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIRC the tooltip is shown on click, not on hover, right? So we need a visual cue (the cursor pointer) to show the element as interactive. Let's keep only the .clickable-tag ::v-deep .bx--tag rule.

:noDefaultToFirst="true"
@tab-selected="tabSelected"
>
<cv-tab

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This file has become very big (~1500 loc), maybe we can try to externalize two components, e.g. TlsCertficatesPanel and AcmeSettingsPanel

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. SettingsTlsCertificates.vue is down to 289 lines: page chrome, tab state, and the list-installed-modules chain whose Traefik instance list both tabs consume. The certificates tab moved to TlsCertificatesPanel.vue, and AcmeSettings.vue is now AcmeSettingsPanel.vue for symmetry.

Two notes on the wiring:

  • The node filter is bound to a query param, and watchQueryData is bound to the view instance in beforeRouteEnter, so the view still owns selectedNodeId and passes it down with .sync.
  • The panel now runs listCertificates() from a watcher on traefikInstances instead of being called at the end of listInstalledModulesCompleted, same trigger AcmeSettingsPanel already used.

I also dropped the selectedNodeLabel computed while moving the code; it was defined and never referenced, already dead on main.

Tested on a live single-node cluster: both tabs, filters, query params, the upload modal and its error mapping, the tooltips, and the ACME edit modal.

Follow review feedback: the search box label repeated the table title, and
the restart notice did not say the page comes back on its own.
styles/_utils.scss is imported globally, so the local justify-flex-end rule
duplicated flex and justify-end. Removing it empties the style block.
The view had grown past 1500 lines. Move the certificates tab into
TlsCertificatesPanel and rename AcmeSettings to AcmeSettingsPanel, leaving
the view with the page chrome, the tab state and the shared Traefik instance
list it feeds to both panels.

The node filter stays bound to a query param, so the view owns it and passes
it down with .sync. Drop the selectedNodeLabel computed, which was never
referenced.

Also narrow the tooltip cursor rule: carbon already sets a pointer on the
tooltip trigger, only .bx--tag overrides it.
Drop comments the code already states and shorten the ones worth keeping.
@stephdl
stephdl requested a review from andre8244 August 26, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants