certs: async renewal via background task + status polling

- POST /acme/renew returns a request_id and spawns a background task
  (renew/deploy/refresh steps); dedups per-domain like issue
- completes as "skipped" when acme.sh reports the renewal window
  has not been reached (no --force)
- new GET /acme/renew/status endpoint (iface + handler + blueprint)
- run acme.sh subprocesses off the event loop (asyncio.to_thread)
  in both issuance and renewal
- ActionCell: busy/busyLabel props; certs page disables the Renew
  button and polls renewal status with toasts for success/skip/fail
This commit is contained in:
2026-08-20 13:10:16 +00:00
parent 332d14e37d
commit 94705490b9
9 changed files with 462 additions and 44 deletions
+5 -2
View File
@@ -314,6 +314,8 @@ export function ServiceStatus(props = {}) {
* @param {string} [props.removeLabel] - Delete button label (default: 'Remove')
* @param {object} [props.removeBody] - Optional JSON body to send with DELETE
* @param {string} [props.editCls] - Override classes for edit button (default: 'btn btn-sm btn-outline')
* @param {boolean} [props.busy] - When true the action button is disabled (in-flight operation)
* @param {string} [props.busyLabel] - Label shown while busy (default: editLabel + '…')
* @param {string} [props.deleteKey] - Unique ID forwarded to ConfirmDelete for pending-delete styling
*/
export function ActionCell(props = {}) {
@@ -321,8 +323,9 @@ export function ActionCell(props = {}) {
h('button', {
class: props.editCls || 'btn btn-sm btn-outline',
style: 'margin-right:4px;',
'on:click': props.editClick,
}, props.editLabel),
disabled: !!props.busy,
'on:click': props.busy ? undefined : props.editClick,
}, props.busy ? (props.busyLabel || (props.editLabel + '…')) : props.editLabel),
ConfirmDelete({
url: props.removeUrl,
message: props.removeMessage,