Merge claude/1
This commit is contained in:
commit
0f53fb0b47
@ -44,8 +44,8 @@ Object.assign(TasksManager.prototype, {
|
|||||||
{ match: /^libreportal system reclaim\b/, title: 'LibrePortal - Reclaim Space' },
|
{ match: /^libreportal system reclaim\b/, title: 'LibrePortal - Reclaim Space' },
|
||||||
{ match: /^libreportal system image rm\b/, title: 'LibrePortal - Remove Images' },
|
{ match: /^libreportal system image rm\b/, title: 'LibrePortal - Remove Images' },
|
||||||
{ match: /^libreportal verify\b/, title: 'LibrePortal - Verify System' },
|
{ match: /^libreportal verify\b/, title: 'LibrePortal - Verify System' },
|
||||||
{ match: /^libreportal system health heal\b/, title: 'LibrePortal - Repair Control Plane' },
|
{ match: /^libreportal system health heal\b/, title: 'LibrePortal - Fix System Issues' },
|
||||||
{ match: /^libreportal system health check\b/, title: 'LibrePortal - Check Control Plane' },
|
{ 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 heal\b/, title: 'LibrePortal - Heal Network' },
|
||||||
{ match: /^libreportal system network check\b/, title: 'LibrePortal - Check 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',
|
'config_update': 'Update Config', 'update_config': 'Update Config',
|
||||||
'system_update': 'Update System', 'system_reclaim': 'Reclaim Space',
|
'system_update': 'Update System', 'system_reclaim': 'Reclaim Space',
|
||||||
'system_image_rm': 'Remove Images', 'verify': 'Verify System',
|
'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_check': 'Check for Updates', 'updater_apply': 'Update',
|
||||||
'updater_apply_all': 'Update All', 'updater_rollback': 'Roll Back',
|
'updater_apply_all': 'Update All', 'updater_rollback': 'Roll Back',
|
||||||
'artifact_apply': 'Apply Hotfix', 'artifact_revert': 'Revert Hotfix',
|
'artifact_apply': 'Apply Hotfix', 'artifact_revert': 'Revert Hotfix',
|
||||||
|
|||||||
@ -13,11 +13,11 @@
|
|||||||
// The backend already SELF-HEALS most of this (the task-processor poll dispatches
|
// 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
|
// `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
|
// 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:
|
// Actions go through the normal task pipeline so progress streams on Tasks:
|
||||||
// * "Repair now" -> task `libreportal system health heal`
|
// * "Fix now" -> task `libreportal system health heal`
|
||||||
// * "Re-check" -> task `libreportal system health check`
|
// * "Re-check" -> task `libreportal system health check`
|
||||||
//
|
//
|
||||||
// This file owns no detection logic — it only reads the status file.
|
// 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;
|
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.setAttribute('aria-label', badge.title);
|
||||||
badge.innerHTML = `
|
badge.innerHTML = `
|
||||||
<span class="health-badge-dot" aria-hidden="true"></span>
|
<span class="health-badge-dot" aria-hidden="true"></span>
|
||||||
@ -155,7 +155,7 @@ class HealthNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const s = this.status;
|
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 = `
|
const heartIcon = `
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<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 = `
|
banner.innerHTML = `
|
||||||
<div class="health-banner-icon" aria-hidden="true">${heartIcon}</div>
|
<div class="health-banner-icon" aria-hidden="true">${heartIcon}</div>
|
||||||
<div class="health-banner-text">
|
<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 class="health-banner-sub">${sub}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="health-banner-actions">
|
<div class="health-banner-actions">
|
||||||
<button type="button" class="health-btn-secondary" id="health-banner-details">Details</button>
|
<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>`;
|
</div>`;
|
||||||
|
|
||||||
const details = banner.querySelector('#health-banner-details');
|
const details = banner.querySelector('#health-banner-details');
|
||||||
@ -219,13 +219,13 @@ class HealthNotifier {
|
|||||||
: '';
|
: '';
|
||||||
|
|
||||||
const statusLine = this._hasIssues()
|
const statusLine = this._hasIssues()
|
||||||
? (s.summary || 'The docker control plane needs attention.')
|
? (s.summary || 'The system needs attention.')
|
||||||
: 'Control plane healthy.';
|
: 'System healthy.';
|
||||||
|
|
||||||
overlay.innerHTML = `
|
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">
|
<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">×</button>
|
<button type="button" class="health-panel-close" id="health-panel-close" aria-label="Close">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="health-panel-status ${this._hasIssues() ? 'is-issue' : ''}">${this._escape(statusLine)}</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('')}
|
${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>
|
</dl>
|
||||||
${loopList}
|
${loopList}
|
||||||
${s.can_auto_heal && this._hasIssues() ? '<p class="health-panel-note">Repair stops any crash-looping container and, if the WebUI’s 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">
|
<div class="health-panel-actions">
|
||||||
<button type="button" class="health-btn-secondary" id="health-panel-check">Re-check</button>
|
<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>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
@ -266,7 +266,7 @@ class HealthNotifier {
|
|||||||
this.closePanel();
|
this.closePanel();
|
||||||
try {
|
try {
|
||||||
await this._createTask('libreportal system health heal');
|
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();
|
this._goToTasks();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._toast('Could not start the repair: ' + e.message, 'error');
|
this._toast('Could not start the repair: ' + e.message, 'error');
|
||||||
@ -276,7 +276,7 @@ class HealthNotifier {
|
|||||||
async checkNow() {
|
async checkNow() {
|
||||||
try {
|
try {
|
||||||
await this._createTask('libreportal system health check');
|
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);
|
setTimeout(() => this.refresh(), 4000);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._toast('Could not re-check health: ' + e.message, 'error');
|
this._toast('Could not re-check health: ' + e.message, 'error');
|
||||||
|
|||||||
@ -22,12 +22,12 @@
|
|||||||
#
|
#
|
||||||
# dockerHealthHeal
|
# dockerHealthHeal
|
||||||
dockerHealthHeal() {
|
dockerHealthHeal() {
|
||||||
isHeader "Healing docker / control-plane health"
|
isHeader "Fixing system issues"
|
||||||
|
|
||||||
dockerHealthScan # populate HEALTH_* (call direct, NOT in $(...))
|
dockerHealthScan # populate HEALTH_* (call direct, NOT in $(...))
|
||||||
|
|
||||||
if [[ "$HEALTH_DAEMON_OK" != "true" ]]; then
|
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
|
declare -f webuiSystemHealthCheck >/dev/null 2>&1 && webuiSystemHealthCheck "force" >/dev/null 2>&1
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@ -42,7 +42,7 @@ dockerHealthHeal() {
|
|||||||
for row in "${HEALTH_CRASHLOOPS[@]}"; do
|
for row in "${HEALTH_CRASHLOOPS[@]}"; do
|
||||||
cname="${row#*|}"; cname="${cname%%|*}" # app|container|rc -> container
|
cname="${row#*|}"; cname="${cname%%|*}" # app|container|rc -> container
|
||||||
[[ "$cname" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] || continue
|
[[ "$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
|
dockerCommandRun "docker stop '$cname'" >/dev/null 2>&1
|
||||||
((acted++))
|
((acted++))
|
||||||
done
|
done
|
||||||
@ -50,14 +50,14 @@ dockerHealthHeal() {
|
|||||||
# 2/3) Restore the WebUI host port if the container is up but unreachable.
|
# 2/3) Restore the WebUI host port if the container is up but unreachable.
|
||||||
if [[ "$HEALTH_WEBUI_RUNNING" == "true" && "$HEALTH_WEBUI_REACHABLE" == "false" ]]; then
|
if [[ "$HEALTH_WEBUI_RUNNING" == "true" && "$HEALTH_WEBUI_REACHABLE" == "false" ]]; then
|
||||||
local webui; webui="$(_healthWebuiContainer)"
|
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
|
dockerCommandRun "docker restart '$webui'" >/dev/null 2>&1
|
||||||
((acted++))
|
((acted++))
|
||||||
sleep 4
|
sleep 4
|
||||||
dockerHealthScan
|
dockerHealthScan
|
||||||
|
|
||||||
if [[ "$HEALTH_WEBUI_REACHABLE" != "true" && "$rootless" == "true" ]]; then
|
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
|
dockerCommandRun "systemctl --user restart docker.service" >/dev/null 2>&1
|
||||||
sleep 8
|
sleep 8
|
||||||
# The core container may have no restart policy — bring it back explicitly.
|
# The core container may have no restart policy — bring it back explicitly.
|
||||||
@ -72,12 +72,12 @@ dockerHealthHeal() {
|
|||||||
dockerHealthScan
|
dockerHealthScan
|
||||||
|
|
||||||
if [[ "$HEALTH_WEBUI_RUNNING" == "true" && "$HEALTH_WEBUI_REACHABLE" == "false" ]]; then
|
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
|
return 1
|
||||||
fi
|
fi
|
||||||
if (( acted == 0 )); then
|
if (( acted == 0 )); then
|
||||||
isSuccessful "Nothing to heal — control plane healthy."
|
isSuccessful "Nothing to fix — system healthy."
|
||||||
else
|
else
|
||||||
isSuccessful "Health heal complete — ${acted} action(s); control plane reachable."
|
isSuccessful "Fixed — ${acted} action(s); WebUI reachable."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ webuiSystemHealthCheck() {
|
|||||||
if [[ "$HEALTH_DAEMON_OK" != "true" ]]; then
|
if [[ "$HEALTH_DAEMON_OK" != "true" ]]; then
|
||||||
# Daemon unreachable — neutral status; never alarm on what we can't check.
|
# Daemon unreachable — neutral status; never alarm on what we can't check.
|
||||||
error_json="\"${HEALTH_SCAN_ERROR//\"/\\\"}\""
|
error_json="\"${HEALTH_SCAN_ERROR//\"/\\\"}\""
|
||||||
summary="Docker daemon unreachable"
|
summary="Docker engine unreachable"
|
||||||
else
|
else
|
||||||
local row app cname rc _a _c
|
local row app cname rc _a _c
|
||||||
for row in "${HEALTH_CRASHLOOPS[@]}"; do
|
for row in "${HEALTH_CRASHLOOPS[@]}"; do
|
||||||
@ -68,12 +68,12 @@ webuiSystemHealthCheck() {
|
|||||||
if [[ -n "$crash_json" || "$webui_unreachable" == "true" ]]; then
|
if [[ -n "$crash_json" || "$webui_unreachable" == "true" ]]; then
|
||||||
healthy="false"; issues_found="true"; can_auto_heal="true"
|
healthy="false"; issues_found="true"; can_auto_heal="true"
|
||||||
if [[ "$webui_unreachable" == "true" ]]; then
|
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
|
else
|
||||||
summary="Crash-looping container(s) churning the network"
|
summary="An app is stuck in a restart loop"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
summary="Control plane healthy"
|
summary="System healthy"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user