Merge claude/1

This commit is contained in:
librelad 2026-07-17 23:11:34 +01:00
commit 0f53fb0b47
4 changed files with 30 additions and 30 deletions

View File

@ -44,8 +44,8 @@ Object.assign(TasksManager.prototype, {
{ match: /^libreportal system reclaim\b/, title: 'LibrePortal - Reclaim Space' },
{ match: /^libreportal system image rm\b/, title: 'LibrePortal - Remove Images' },
{ match: /^libreportal verify\b/, title: 'LibrePortal - Verify System' },
{ match: /^libreportal system health heal\b/, title: 'LibrePortal - Repair Control Plane' },
{ match: /^libreportal system health check\b/, title: 'LibrePortal - Check Control Plane' },
{ match: /^libreportal system health heal\b/, title: 'LibrePortal - Fix System Issues' },
{ match: /^libreportal system health check\b/, title: 'LibrePortal - System Health Check' },
{ match: /^libreportal system network heal\b/, title: 'LibrePortal - Heal Network' },
{ match: /^libreportal system network check\b/, title: 'LibrePortal - Check Network' },
@ -142,7 +142,7 @@ Object.assign(TasksManager.prototype, {
'config_update': 'Update Config', 'update_config': 'Update Config',
'system_update': 'Update System', 'system_reclaim': 'Reclaim Space',
'system_image_rm': 'Remove Images', 'verify': 'Verify System',
'system_health_heal': 'Repair Control Plane', 'system_network_heal': 'Heal Network',
'system_health_heal': 'Fix System Issues', 'system_network_heal': 'Heal Network',
'updater_check': 'Check for Updates', 'updater_apply': 'Update',
'updater_apply_all': 'Update All', 'updater_rollback': 'Roll Back',
'artifact_apply': 'Apply Hotfix', 'artifact_revert': 'Revert Hotfix',

View File

@ -13,11 +13,11 @@
// The backend already SELF-HEALS most of this (the task-processor poll dispatches
// `system health heal` when the WebUI's own port is down — you can't click a
// button on a dead WebUI). These surfaces are for visibility and for a manual
// "Repair now" when the WebUI is still reachable (e.g. a crash-loop caught early).
// "Fix now" when the WebUI is still reachable (e.g. a crash-loop caught early).
//
// Actions go through the normal task pipeline so progress streams on Tasks:
// * "Repair now" -> task `libreportal system health heal`
// * "Re-check" -> task `libreportal system health check`
// * "Fix now" -> task `libreportal system health heal`
// * "Re-check" -> task `libreportal system health check`
//
// This file owns no detection logic — it only reads the status file.
@ -120,7 +120,7 @@ class HealthNotifier {
}
const n = Array.isArray(this.status.crash_loops) ? this.status.crash_loops.length : 0;
badge.title = 'Control plane needs attention';
badge.title = 'System needs attention';
badge.setAttribute('aria-label', badge.title);
badge.innerHTML = `
<span class="health-badge-dot" aria-hidden="true"></span>
@ -155,7 +155,7 @@ class HealthNotifier {
}
const s = this.status;
const sub = s.summary ? this._escape(s.summary) : 'The docker control plane needs attention';
const sub = s.summary ? this._escape(s.summary) : 'The system needs attention';
const heartIcon = `
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@ -166,12 +166,12 @@ class HealthNotifier {
banner.innerHTML = `
<div class="health-banner-icon" aria-hidden="true">${heartIcon}</div>
<div class="health-banner-text">
<div class="health-banner-title">Control plane attention needed</div>
<div class="health-banner-title">System needs attention</div>
<div class="health-banner-sub">${sub}</div>
</div>
<div class="health-banner-actions">
<button type="button" class="health-btn-secondary" id="health-banner-details">Details</button>
${s.can_auto_heal ? '<button type="button" class="health-btn-primary" id="health-banner-heal">Repair now</button>' : ''}
${s.can_auto_heal ? '<button type="button" class="health-btn-primary" id="health-banner-heal">Fix now</button>' : ''}
</div>`;
const details = banner.querySelector('#health-banner-details');
@ -219,13 +219,13 @@ class HealthNotifier {
: '';
const statusLine = this._hasIssues()
? (s.summary || 'The docker control plane needs attention.')
: 'Control plane healthy.';
? (s.summary || 'The system needs attention.')
: 'System healthy.';
overlay.innerHTML = `
<div class="health-panel" role="dialog" aria-modal="true" aria-label="Control plane health">
<div class="health-panel" role="dialog" aria-modal="true" aria-label="System health">
<div class="health-panel-header">
<h3>Control Plane Health</h3>
<h3>System Health</h3>
<button type="button" class="health-panel-close" id="health-panel-close" aria-label="Close">&times;</button>
</div>
<div class="health-panel-status ${this._hasIssues() ? 'is-issue' : ''}">${this._escape(statusLine)}</div>
@ -234,10 +234,10 @@ class HealthNotifier {
${rows.map(([k, v, cls]) => `<div class="health-panel-row"><dt>${this._escape(k)}</dt><dd class="${cls || ''}">${this._escape(v)}</dd></div>`).join('')}
</dl>
${loopList}
${s.can_auto_heal && this._hasIssues() ? '<p class="health-panel-note">Repair stops any crash-looping container and, if the WebUIs host port is down, restarts it — recycling the rootless docker daemon only if needed. Progress streams on the Tasks page.</p>' : ''}
${s.can_auto_heal && this._hasIssues() ? '<p class="health-panel-note">Fixing stops any app stuck in a restart loop and, if the WebUI is unreachable, restarts it — restarting Docker only if needed. Progress streams on the Tasks page.</p>' : ''}
<div class="health-panel-actions">
<button type="button" class="health-btn-secondary" id="health-panel-check">Re-check</button>
${s.can_auto_heal && this._hasIssues() ? '<button type="button" class="health-btn-primary" id="health-panel-heal">Repair now</button>' : ''}
${s.can_auto_heal && this._hasIssues() ? '<button type="button" class="health-btn-primary" id="health-panel-heal">Fix now</button>' : ''}
</div>
</div>`;
@ -266,7 +266,7 @@ class HealthNotifier {
this.closePanel();
try {
await this._createTask('libreportal system health heal');
this._toast('Control-plane repair started — follow progress on the Tasks page.', 'info');
this._toast('Fixing system issues — follow progress on the Tasks page.', 'info');
this._goToTasks();
} catch (e) {
this._toast('Could not start the repair: ' + e.message, 'error');
@ -276,7 +276,7 @@ class HealthNotifier {
async checkNow() {
try {
await this._createTask('libreportal system health check');
this._toast('Re-checking control-plane health…', 'info');
this._toast('Re-checking system health…', 'info');
setTimeout(() => this.refresh(), 4000);
} catch (e) {
this._toast('Could not re-check health: ' + e.message, 'error');

View File

@ -22,12 +22,12 @@
#
# dockerHealthHeal
dockerHealthHeal() {
isHeader "Healing docker / control-plane health"
isHeader "Fixing system issues"
dockerHealthScan # populate HEALTH_* (call direct, NOT in $(...))
if [[ "$HEALTH_DAEMON_OK" != "true" ]]; then
isError "Docker daemon unreachable — cannot heal from here."
isError "Docker engine unreachable — cannot fix from here."
declare -f webuiSystemHealthCheck >/dev/null 2>&1 && webuiSystemHealthCheck "force" >/dev/null 2>&1
return 1
fi
@ -42,7 +42,7 @@ dockerHealthHeal() {
for row in "${HEALTH_CRASHLOOPS[@]}"; do
cname="${row#*|}"; cname="${cname%%|*}" # app|container|rc -> container
[[ "$cname" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] || continue
isNotice "Stopping crash-looping container '$cname' (restart storm churning the network)…"
isNotice "Stopping '$cname' — stuck in a restart loop (churning the network)…"
dockerCommandRun "docker stop '$cname'" >/dev/null 2>&1
((acted++))
done
@ -50,14 +50,14 @@ dockerHealthHeal() {
# 2/3) Restore the WebUI host port if the container is up but unreachable.
if [[ "$HEALTH_WEBUI_RUNNING" == "true" && "$HEALTH_WEBUI_REACHABLE" == "false" ]]; then
local webui; webui="$(_healthWebuiContainer)"
isNotice "WebUI up but host port ${HEALTH_WEBUI_PORT:-?} unreachable — restarting '$webui' to re-publish"
isNotice "WebUI unreachable on port ${HEALTH_WEBUI_PORT:-?} — restarting '$webui' to restore access"
dockerCommandRun "docker restart '$webui'" >/dev/null 2>&1
((acted++))
sleep 4
dockerHealthScan
if [[ "$HEALTH_WEBUI_REACHABLE" != "true" && "$rootless" == "true" ]]; then
isNotice "Still unreachable — recycling the rootless docker daemon to rebuild port-forward state…"
isNotice "Still unreachable — restarting Docker to restore access…"
dockerCommandRun "systemctl --user restart docker.service" >/dev/null 2>&1
sleep 8
# The core container may have no restart policy — bring it back explicitly.
@ -72,12 +72,12 @@ dockerHealthHeal() {
dockerHealthScan
if [[ "$HEALTH_WEBUI_RUNNING" == "true" && "$HEALTH_WEBUI_REACHABLE" == "false" ]]; then
isError "Health heal ran ${acted} action(s); WebUI still unreachable on port ${HEALTH_WEBUI_PORT:-?} — manual inspection needed."
isError "Ran ${acted} fix action(s); WebUI still unreachable on port ${HEALTH_WEBUI_PORT:-?} — manual check needed."
return 1
fi
if (( acted == 0 )); then
isSuccessful "Nothing to heal — control plane healthy."
isSuccessful "Nothing to fix — system healthy."
else
isSuccessful "Health heal complete — ${acted} action(s); control plane reachable."
isSuccessful "Fixed — ${acted} action(s); WebUI reachable."
fi
}

View File

@ -50,7 +50,7 @@ webuiSystemHealthCheck() {
if [[ "$HEALTH_DAEMON_OK" != "true" ]]; then
# Daemon unreachable — neutral status; never alarm on what we can't check.
error_json="\"${HEALTH_SCAN_ERROR//\"/\\\"}\""
summary="Docker daemon unreachable"
summary="Docker engine unreachable"
else
local row app cname rc _a _c
for row in "${HEALTH_CRASHLOOPS[@]}"; do
@ -68,12 +68,12 @@ webuiSystemHealthCheck() {
if [[ -n "$crash_json" || "$webui_unreachable" == "true" ]]; then
healthy="false"; issues_found="true"; can_auto_heal="true"
if [[ "$webui_unreachable" == "true" ]]; then
summary="WebUI unreachable on host port ${HEALTH_WEBUI_PORT:-?} (container is up) — port forward needs repair"
summary="WebUI unreachable on port ${HEALTH_WEBUI_PORT:-?} — needs a fix"
else
summary="Crash-looping container(s) churning the network"
summary="An app is stuck in a restart loop"
fi
else
summary="Control plane healthy"
summary="System healthy"
fi
fi