mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-08-02 05:46:21 +00:00
Update 1.2.2.2 beta
This commit is contained in:
Binary file not shown.
@@ -1 +1 @@
|
|||||||
30c427079c3b2b829ece1b801f045c76b8dd7d16286b5ce243f56a5df9928eb8 ProxMenux-1.2.2.2-beta.AppImage
|
6bd898c3801bdf27b983ca5c107421d485e163f0c339cbc5f5cc90c9bb26a4d6 ProxMenux-1.2.2.2-beta.AppImage
|
||||||
|
|||||||
@@ -66,11 +66,17 @@ const CustomMemoryTooltip = ({ active, payload, label }: any) => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MetricsError {
|
||||||
|
headline: string
|
||||||
|
details?: string
|
||||||
|
suggestion?: string
|
||||||
|
}
|
||||||
|
|
||||||
export function NodeMetricsCharts() {
|
export function NodeMetricsCharts() {
|
||||||
const [timeframe, setTimeframe] = useState("day")
|
const [timeframe, setTimeframe] = useState("day")
|
||||||
const [data, setData] = useState<NodeMetricsData[]>([])
|
const [data, setData] = useState<NodeMetricsData[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<MetricsError | null>(null)
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
const [visibleLines, setVisibleLines] = useState({
|
const [visibleLines, setVisibleLines] = useState({
|
||||||
@@ -160,9 +166,16 @@ export function NodeMetricsCharts() {
|
|||||||
setData(transformedData)
|
setData(transformedData)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Error fetching node metrics:", err)
|
console.error("Error fetching node metrics:", err)
|
||||||
console.error("Error message:", err.message)
|
// fetchApi attaches the parsed JSON body to err.body. The metrics
|
||||||
console.error("Error stack:", err.stack)
|
// endpoint enriches 503 responses with `details` (Proxmox-side
|
||||||
setError(err.message || "Error loading metrics")
|
// diagnostic) and `suggestion` (how to fix). Pull them through so
|
||||||
|
// the user sees actionable text instead of a bare "503".
|
||||||
|
const body = err?.body
|
||||||
|
setError({
|
||||||
|
headline: body?.error || err?.message || "Error loading metrics",
|
||||||
|
details: body?.details,
|
||||||
|
suggestion: body?.suggestion,
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -231,24 +244,36 @@ export function NodeMetricsCharts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
// Both panels carry the same error — render an identical card on
|
||||||
|
// each side. The headline is the short cause, the details block
|
||||||
|
// explains it's a Proxmox-host issue (not a Monitor bug), and the
|
||||||
|
// suggestion is the exact command the operator should run.
|
||||||
|
const errorCard = (
|
||||||
|
<Card className="bg-card border-border">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex flex-col items-start justify-center h-[300px] gap-2 px-2 overflow-auto">
|
||||||
|
<p className="text-sm font-semibold text-red-400">{error.headline}</p>
|
||||||
|
{error.details && (
|
||||||
|
<p className="text-xs text-muted-foreground leading-relaxed">{error.details}</p>
|
||||||
|
)}
|
||||||
|
{error.suggestion && (
|
||||||
|
<div className="w-full mt-2">
|
||||||
|
<p className="text-[10px] uppercase tracking-wide text-muted-foreground mb-1">
|
||||||
|
Suggested fix on the Proxmox host
|
||||||
|
</p>
|
||||||
|
<code className="block text-xs bg-background/60 border border-border rounded px-2 py-1.5 font-mono break-all">
|
||||||
|
{error.suggestion}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<Card className="bg-card border-border">
|
{errorCard}
|
||||||
<CardContent className="p-6">
|
{errorCard}
|
||||||
<div className="flex flex-col items-center justify-center h-[300px] gap-2">
|
|
||||||
<p className="text-muted-foreground text-sm">Metrics data not available yet</p>
|
|
||||||
<p className="text-xs text-red-500">{error}</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className="bg-card border-border">
|
|
||||||
<CardContent className="p-6">
|
|
||||||
<div className="flex flex-col items-center justify-center h-[300px] gap-2">
|
|
||||||
<p className="text-muted-foreground text-sm">Metrics data not available yet</p>
|
|
||||||
<p className="text-xs text-red-500">{error}</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,12 @@ export async function fetchApi<T>(endpoint: string, options?: RequestInit): Prom
|
|||||||
// return `{error: "..."}` on failure (e.g. /api/vms/<id>/control
|
// return `{error: "..."}` on failure (e.g. /api/vms/<id>/control
|
||||||
// includes the pvesh stderr — telling the user "no space left on
|
// includes the pvesh stderr — telling the user "no space left on
|
||||||
// device" is infinitely more useful than the raw status text).
|
// device" is infinitely more useful than the raw status text).
|
||||||
|
//
|
||||||
|
// We also attach the FULL parsed JSON body to the thrown Error
|
||||||
|
// as `.body` so callers that want the optional `details` /
|
||||||
|
// `suggestion` fields (e.g. /api/node/metrics) can render them
|
||||||
|
// without re-fetching. Callers that just read `err.message`
|
||||||
|
// keep working exactly as before.
|
||||||
try {
|
try {
|
||||||
const ct = response.headers.get("content-type") || ""
|
const ct = response.headers.get("content-type") || ""
|
||||||
if (ct.includes("application/json")) {
|
if (ct.includes("application/json")) {
|
||||||
@@ -145,7 +151,10 @@ export async function fetchApi<T>(endpoint: string, options?: RequestInit): Prom
|
|||||||
const detail =
|
const detail =
|
||||||
(body && (body.error || body.message)) || ""
|
(body && (body.error || body.message)) || ""
|
||||||
if (detail) {
|
if (detail) {
|
||||||
throw new Error(detail)
|
const e: Error & { body?: unknown; status?: number } = new Error(detail)
|
||||||
|
e.body = body
|
||||||
|
e.status = response.status
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (parseErr) {
|
} catch (parseErr) {
|
||||||
|
|||||||
@@ -9415,7 +9415,13 @@ def _update_smart_cron():
|
|||||||
test_type = schedule.get('test_type', 'short')
|
test_type = schedule.get('test_type', 'short')
|
||||||
retention = schedule.get('retention', 10)
|
retention = schedule.get('retention', 10)
|
||||||
|
|
||||||
cmd = f'/usr/local/share/proxmenux/scripts/smart-scheduled-test.sh --schedule-id {schedule_id} --test-type {test_type} --retention {retention}'
|
# The installer keeps the storage helpers under
|
||||||
|
# /usr/local/share/proxmenux/scripts/storage/, mirroring the
|
||||||
|
# repo layout. The previous path (without /storage/) generated
|
||||||
|
# cron lines that pointed at a non-existent file, so the timer
|
||||||
|
# fired, cron logged nothing, and the operator saw "no
|
||||||
|
# scheduled tests in history" with no obvious clue why.
|
||||||
|
cmd = f'/usr/local/share/proxmenux/scripts/storage/smart-scheduled-test.sh --schedule-id {schedule_id} --test-type {test_type} --retention {retention}'
|
||||||
if disks != ['all']:
|
if disks != ['all']:
|
||||||
cmd += f" --disks '{','.join(disks)}'"
|
cmd += f" --disks '{','.join(disks)}'"
|
||||||
|
|
||||||
@@ -9999,6 +10005,57 @@ def api_node_metrics():
|
|||||||
'--timeframe', timeframe, '--output-format', 'json'],
|
'--timeframe', timeframe, '--output-format', 'json'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True, timeout=10)
|
||||||
|
|
||||||
|
# Detect well-known Proxmox-side failures BEFORE trying to parse
|
||||||
|
# the JSON. These are PVE host problems (rrdcached down, RRD file
|
||||||
|
# corrupt, node-name mismatch). None of them are caused by the
|
||||||
|
# Monitor itself — surface a specific message so the operator
|
||||||
|
# doesn't blame ProxMenux for a Proxmox-host data-store issue.
|
||||||
|
if rrd_result.returncode != 0:
|
||||||
|
stderr_str = (rrd_result.stderr or '') + (rrd_result.stdout or '')
|
||||||
|
stderr_lower = stderr_str.lower()
|
||||||
|
if 'mmaping file' in stderr_lower and 'invalid argument' in stderr_lower:
|
||||||
|
# Corrupt RRD file on disk. Operator must recreate it.
|
||||||
|
return jsonify({
|
||||||
|
'error': 'Proxmox RRD database is corrupt',
|
||||||
|
'details': (
|
||||||
|
'The host metrics file Proxmox keeps under '
|
||||||
|
'/var/lib/rrdcached/db/pve-node-9.0/ failed to '
|
||||||
|
'memory-map (Invalid argument). This is a Proxmox-side '
|
||||||
|
'data-store issue, not a Monitor bug.'
|
||||||
|
),
|
||||||
|
'suggestion': (
|
||||||
|
'Stop pvestatd + pve-cluster + rrdcached, move the '
|
||||||
|
'broken RRD aside, restart the services. Proxmox will '
|
||||||
|
'rebuild the RRD from scratch (history is lost).'
|
||||||
|
),
|
||||||
|
'raw': stderr_str.strip()[:500],
|
||||||
|
}), 503
|
||||||
|
if 'no such file' in stderr_lower or 'no such node' in stderr_lower or 'does not exist' in stderr_lower:
|
||||||
|
return jsonify({
|
||||||
|
'error': 'Proxmox node name mismatch',
|
||||||
|
'details': (
|
||||||
|
f"pvesh could not find node '{local_node}'. The "
|
||||||
|
'usual cause is that the host was renamed after '
|
||||||
|
'Proxmox was installed, so /etc/pve/nodes/ still '
|
||||||
|
'carries the old name. This is a Proxmox-side '
|
||||||
|
'config issue, not a Monitor bug.'
|
||||||
|
),
|
||||||
|
'suggestion': 'Compare `hostname` with `ls /etc/pve/nodes/` — they must match.',
|
||||||
|
'raw': stderr_str.strip()[:500],
|
||||||
|
}), 503
|
||||||
|
if 'rrd' in stderr_lower or 'empty' in stderr_lower:
|
||||||
|
return jsonify({
|
||||||
|
'error': 'Proxmox RRD data not available',
|
||||||
|
'details': 'The RRD database appears empty. Proxmox may not have collected metrics yet (fresh install) or rrdcached was down at boot.',
|
||||||
|
'suggestion': 'systemctl restart rrdcached pvestatd ; wait ~5 min and reload this page.',
|
||||||
|
'raw': stderr_str.strip()[:500],
|
||||||
|
}), 503
|
||||||
|
return jsonify({
|
||||||
|
'error': 'Proxmox metrics command failed',
|
||||||
|
'details': 'pvesh exited non-zero. Check Proxmox host status.',
|
||||||
|
'raw': stderr_str.strip()[:500],
|
||||||
|
}), 503
|
||||||
|
|
||||||
if rrd_result.returncode == 0:
|
if rrd_result.returncode == 0:
|
||||||
rrd_data = json.loads(rrd_result.stdout)
|
rrd_data = json.loads(rrd_result.stdout)
|
||||||
|
|
||||||
@@ -10044,26 +10101,19 @@ def api_node_metrics():
|
|||||||
}
|
}
|
||||||
_node_metrics_cache_set(timeframe, payload)
|
_node_metrics_cache_set(timeframe, payload)
|
||||||
return jsonify(payload)
|
return jsonify(payload)
|
||||||
else:
|
# Note: the old `else` branch that handled rrd_result.returncode != 0
|
||||||
# Check if RRD file is empty or corrupted
|
# was removed — the early-return block above now catches every
|
||||||
stderr_lower = rrd_result.stderr.lower() if rrd_result.stderr else ''
|
# non-zero exit BEFORE we ever attempt json.loads(), so reaching
|
||||||
if 'rrd' in stderr_lower or 'no such file' in stderr_lower or 'empty' in stderr_lower:
|
# this point with returncode != 0 is impossible.
|
||||||
return jsonify({
|
|
||||||
'error': 'RRD data not available',
|
|
||||||
'details': 'The RRD database file may be empty or corrupted. This can happen if rrdcached was not running properly after Proxmox installation.',
|
|
||||||
'suggestion': 'Try restarting rrdcached: systemctl restart rrdcached'
|
|
||||||
}), 503 # Service Unavailable - more appropriate than 500
|
|
||||||
return jsonify({'error': f'Failed to get RRD data: {rrd_result.stderr}'}), 500
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
# pvesh returned invalid JSON - likely empty RRD
|
# pvesh returned invalid JSON - likely empty RRD
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'error': 'RRD data not available',
|
'error': 'Proxmox RRD data not available',
|
||||||
'details': 'Unable to parse metrics data. The RRD database may be empty or corrupted.',
|
'details': 'pvesh returned non-JSON output. The RRD database is likely empty (fresh install where pvestatd has not run yet) or the rrdcached daemon is down.',
|
||||||
'suggestion': 'Try restarting rrdcached: systemctl restart rrdcached'
|
'suggestion': 'systemctl restart rrdcached pvestatd ; wait ~5 min and reload.',
|
||||||
}), 503
|
}), 503
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/api/logs/counts', methods=['GET'])
|
@app.route('/api/logs/counts', methods=['GET'])
|
||||||
@@ -16711,6 +16761,17 @@ if __name__ == '__main__':
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
# Regenerate /etc/cron.d/proxmenux-smart from the current schedule
|
||||||
|
# config on every Monitor start. Pre-existing installs may carry
|
||||||
|
# a cron file with the broken script path (without /storage/),
|
||||||
|
# left over from before the path-fix in this build. Rewriting it
|
||||||
|
# at startup is idempotent and silently heals those hosts on the
|
||||||
|
# first restart after the upgrade — no operator action needed.
|
||||||
|
try:
|
||||||
|
_update_smart_cron()
|
||||||
|
except Exception as _e:
|
||||||
|
print(f"[startup] smart cron regen failed: {_e}", file=sys.stderr)
|
||||||
|
|
||||||
# Custom filter to suppress TLS handshake noise when running HTTP
|
# Custom filter to suppress TLS handshake noise when running HTTP
|
||||||
# (browsers may cache HTTPS and keep sending TLS ClientHello to an HTTP server)
|
# (browsers may cache HTTPS and keep sending TLS ClientHello to an HTTP server)
|
||||||
class TLSNoiseFilter(logging.Filter):
|
class TLSNoiseFilter(logging.Filter):
|
||||||
|
|||||||
Reference in New Issue
Block a user