mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-10-02 08:06:17 +00:00
Update AppImage
This commit is contained in:
BIN
AppImage/public/favicon.ico
Normal file
BIN
AppImage/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@@ -194,9 +194,20 @@ chmod +x "$APP_DIR/usr/bin/translate_cli.py"
|
|||||||
# Copy Next.js build
|
# Copy Next.js build
|
||||||
echo "📋 Copying web dashboard..."
|
echo "📋 Copying web dashboard..."
|
||||||
if [ -d "$APPIMAGE_ROOT/.next" ]; then
|
if [ -d "$APPIMAGE_ROOT/.next" ]; then
|
||||||
|
mkdir -p "$APP_DIR/web"
|
||||||
cp -r "$APPIMAGE_ROOT/.next" "$APP_DIR/web/"
|
cp -r "$APPIMAGE_ROOT/.next" "$APP_DIR/web/"
|
||||||
cp -r "$APPIMAGE_ROOT/public" "$APP_DIR/web/"
|
cp -r "$APPIMAGE_ROOT/public" "$APP_DIR/web/"
|
||||||
cp "$APPIMAGE_ROOT/package.json" "$APP_DIR/web/"
|
cp "$APPIMAGE_ROOT/package.json" "$APP_DIR/web/"
|
||||||
|
|
||||||
|
# Also try to create a static export if possible
|
||||||
|
cd "$APPIMAGE_ROOT"
|
||||||
|
if npm run export 2>/dev/null; then
|
||||||
|
echo "✅ Next.js static export created"
|
||||||
|
if [ -d "out" ]; then
|
||||||
|
cp -r "out" "$APP_DIR/web/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo "✅ Next.js build copied successfully"
|
echo "✅ Next.js build copied successfully"
|
||||||
else
|
else
|
||||||
echo "❌ Error: Next.js build not found even after building"
|
echo "❌ Error: Next.js build not found even after building"
|
||||||
|
@@ -21,419 +21,82 @@ CORS(app) # Enable CORS for Next.js frontend
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def serve_dashboard():
|
def serve_dashboard():
|
||||||
"""Serve the main dashboard page"""
|
"""Serve the main dashboard page from Next.js build"""
|
||||||
try:
|
try:
|
||||||
web_dir = os.path.join(os.path.dirname(__file__), '..', '.next', 'static')
|
next_dir = os.path.join(os.path.dirname(__file__), '..', 'web', '.next')
|
||||||
index_file = os.path.join(os.path.dirname(__file__), '..', '.next', 'server', 'app', 'page.html')
|
|
||||||
|
|
||||||
if os.path.exists(index_file):
|
# Try to serve the Next.js built index page
|
||||||
return send_file(index_file)
|
index_paths = [
|
||||||
else:
|
os.path.join(next_dir, 'server', 'app', 'page.html'),
|
||||||
return '''
|
os.path.join(next_dir, 'server', 'pages', 'index.html'),
|
||||||
<!DOCTYPE html>
|
os.path.join(os.path.dirname(__file__), '..', 'web', 'out', 'index.html'),
|
||||||
<html lang="en">
|
os.path.join(os.path.dirname(__file__), '..', 'web', 'dist', 'index.html')
|
||||||
<head>
|
]
|
||||||
<title>ProxMenux Monitor</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="Proxmox System Monitoring Dashboard">
|
|
||||||
<meta name="theme-color" content="#4f46e5">
|
|
||||||
<link rel="manifest" href="/manifest.json">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.jpg">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.jpg">
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.jpg">
|
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
background: #0a0a0a;
|
|
||||||
color: #fff;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background: #111;
|
|
||||||
border-bottom: 1px solid #333;
|
|
||||||
padding: 1rem 2rem;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 50;
|
|
||||||
}
|
|
||||||
.header-content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.logo-section { display: flex; align-items: center; gap: 1rem; }
|
|
||||||
.logo { width: 40px; height: 40px; border-radius: 8px; }
|
|
||||||
.title { font-size: 1.25rem; font-weight: 600; }
|
|
||||||
.subtitle { font-size: 0.875rem; color: #888; }
|
|
||||||
.server-info { display: flex; align-items: center; gap: 0.5rem; color: #888; }
|
|
||||||
.status-section { display: flex; align-items: center; gap: 1rem; }
|
|
||||||
.status-badge {
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
color: #10b981;
|
|
||||||
border: 1px solid rgba(16, 185, 129, 0.2);
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid #333;
|
|
||||||
color: #fff;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
.btn:hover { background: #222; }
|
|
||||||
.container { max-width: 1200px; margin: 0 auto; padding: 2rem; }
|
|
||||||
.tabs { margin-bottom: 2rem; }
|
|
||||||
.tab-list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, 1fr);
|
|
||||||
background: #111;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.tab {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: #888;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
.tab.active { background: #4f46e5; color: white; }
|
|
||||||
.tab:hover:not(.active) { background: #222; color: #fff; }
|
|
||||||
.metrics-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
.metric-card {
|
|
||||||
background: #111;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
.metric-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.metric-title { color: #888; font-size: 0.875rem; }
|
|
||||||
.metric-value { font-size: 2rem; font-weight: 700; margin-bottom: 0.5rem; }
|
|
||||||
.progress-bar {
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
background: #333;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
.progress-fill { height: 100%; background: #4f46e5; transition: width 0.3s; }
|
|
||||||
.metric-info { color: #888; font-size: 0.75rem; }
|
|
||||||
.charts-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
||||||
gap: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
.chart-card {
|
|
||||||
background: #111;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
.chart-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.chart-placeholder {
|
|
||||||
height: 200px;
|
|
||||||
background: #0a0a0a;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
.info-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
.info-card {
|
|
||||||
background: #111;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
.info-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.info-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
.info-label { color: #888; }
|
|
||||||
.info-value { font-weight: 500; }
|
|
||||||
.badge {
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.badge-green { background: rgba(16, 185, 129, 0.1); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.2); }
|
|
||||||
.badge-blue { background: rgba(59, 130, 246, 0.1); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.2); }
|
|
||||||
.badge-yellow { background: rgba(245, 158, 11, 0.1); color: #f59e0b; border: 1px solid rgba(245, 158, 11, 0.2); }
|
|
||||||
.footer {
|
|
||||||
margin-top: 3rem;
|
|
||||||
padding-top: 1.5rem;
|
|
||||||
border-top: 1px solid #333;
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="header">
|
|
||||||
<div class="header-content">
|
|
||||||
<div class="logo-section">
|
|
||||||
<img src="/images/proxmenux-logo.png" alt="ProxMenux Logo" class="logo">
|
|
||||||
<div>
|
|
||||||
<div class="title">ProxMenux Monitor</div>
|
|
||||||
<div class="subtitle">Proxmox System Dashboard</div>
|
|
||||||
</div>
|
|
||||||
<div class="server-info">
|
|
||||||
<span>📊</span>
|
|
||||||
<div>
|
|
||||||
<div>proxmox-01</div>
|
|
||||||
<div style="font-size: 0.75rem;">pve-node-01</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="status-section">
|
|
||||||
<div class="status-badge">✅ Healthy</div>
|
|
||||||
<div style="color: #888; font-size: 0.875rem;">Uptime: 15d 7h 23m</div>
|
|
||||||
<button class="btn" onclick="location.reload()">🔄 Refresh</button>
|
|
||||||
<button class="btn">🌙 Theme</button>
|
|
||||||
<button class="btn">🌐 Translate</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="container">
|
for index_path in index_paths:
|
||||||
<div class="tabs">
|
if os.path.exists(index_path):
|
||||||
<div class="tab-list">
|
return send_file(index_path)
|
||||||
<button class="tab active">Overview</button>
|
|
||||||
<button class="tab">Storage</button>
|
|
||||||
<button class="tab">Network</button>
|
|
||||||
<button class="tab">Virtual Machines</button>
|
|
||||||
<button class="tab">System Logs</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="metrics-grid">
|
# If no Next.js build found, return error message
|
||||||
<div class="metric-card">
|
return '''
|
||||||
<div class="metric-header">
|
<!DOCTYPE html>
|
||||||
<div class="metric-title">CPU Usage</div>
|
<html>
|
||||||
<span>🖥️</span>
|
<head><title>ProxMenux Monitor - Build Error</title></head>
|
||||||
</div>
|
<body style="font-family: Arial; padding: 2rem; background: #0a0a0a; color: #fff;">
|
||||||
<div class="metric-value">67.3%</div>
|
<h1>🚨 ProxMenux Monitor - Build Error</h1>
|
||||||
<div class="progress-bar">
|
<p>Next.js application not found. The AppImage may not have been built correctly.</p>
|
||||||
<div class="progress-fill" style="width: 67.3%"></div>
|
<p>Expected paths checked:</p>
|
||||||
</div>
|
<ul>''' + ''.join([f'<li>{path}</li>' for path in index_paths]) + '''</ul>
|
||||||
<div class="metric-info">↓ 2.1% from last hour</div>
|
<p>API endpoints are still available:</p>
|
||||||
</div>
|
<ul>
|
||||||
|
<li><a href="/api/system" style="color: #4f46e5;">/api/system</a></li>
|
||||||
|
<li><a href="/api/system-info" style="color: #4f46e5;">/api/system-info</a></li>
|
||||||
|
<li><a href="/api/storage" style="color: #4f46e5;">/api/storage</a></li>
|
||||||
|
<li><a href="/api/network" style="color: #4f46e5;">/api/network</a></li>
|
||||||
|
<li><a href="/api/vms" style="color: #4f46e5;">/api/vms</a></li>
|
||||||
|
<li><a href="/api/health" style="color: #4f46e5;">/api/health</a></li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''', 500
|
||||||
|
|
||||||
<div class="metric-card">
|
|
||||||
<div class="metric-header">
|
|
||||||
<div class="metric-title">Memory Usage</div>
|
|
||||||
<span>💾</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric-value">15.8 GB</div>
|
|
||||||
<div class="progress-bar">
|
|
||||||
<div class="progress-fill" style="width: 49.4%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="metric-info">49.4% of 32 GB • ↑ 1.2 GB</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="metric-card">
|
|
||||||
<div class="metric-header">
|
|
||||||
<div class="metric-title">Temperature</div>
|
|
||||||
<span>🌡️</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric-value">52°C</div>
|
|
||||||
<div style="margin: 0.5rem 0;">
|
|
||||||
<span class="badge badge-green">Normal</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric-info">Max: 78°C • Avg: 48°C</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="metric-card">
|
|
||||||
<div class="metric-header">
|
|
||||||
<div class="metric-title">Active VMs</div>
|
|
||||||
<span>🖥️</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric-value">12</div>
|
|
||||||
<div style="margin: 0.5rem 0; display: flex; flex-wrap: wrap; gap: 0.25rem;">
|
|
||||||
<span class="badge badge-green">8 Running</span>
|
|
||||||
<span class="badge badge-yellow">4 Stopped</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric-info">Total: 16 VMs configured</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="charts-grid">
|
|
||||||
<div class="chart-card">
|
|
||||||
<div class="chart-title">
|
|
||||||
<span>📈</span>
|
|
||||||
CPU Usage (24h)
|
|
||||||
</div>
|
|
||||||
<div class="chart-placeholder">
|
|
||||||
CPU Usage Chart<br>
|
|
||||||
<small style="color: #888;">Real-time data from /api/system</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chart-card">
|
|
||||||
<div class="chart-title">
|
|
||||||
<span>💾</span>
|
|
||||||
Memory Usage (24h)
|
|
||||||
</div>
|
|
||||||
<div class="chart-placeholder">
|
|
||||||
Memory Usage Chart<br>
|
|
||||||
<small style="color: #888;">Real-time data from /api/system</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="info-title">
|
|
||||||
<span>🖥️</span>
|
|
||||||
System Information
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Hostname:</span>
|
|
||||||
<span class="info-value">proxmox-01</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Version:</span>
|
|
||||||
<span class="info-value">PVE 8.1.3</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Kernel:</span>
|
|
||||||
<span class="info-value">6.5.11-7-pve</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Architecture:</span>
|
|
||||||
<span class="info-value">x86_64</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="info-title">
|
|
||||||
<span>👥</span>
|
|
||||||
Active Sessions
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Web Console:</span>
|
|
||||||
<span class="badge badge-green">3 active</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">SSH Sessions:</span>
|
|
||||||
<span class="badge badge-blue">1 active</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">API Calls:</span>
|
|
||||||
<span class="info-value">247/hour</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="info-title">
|
|
||||||
<span>⚡</span>
|
|
||||||
Power & Performance
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Power State:</span>
|
|
||||||
<span class="badge badge-green">Running</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Load Average:</span>
|
|
||||||
<span class="info-value">1.23, 1.45, 1.67</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Boot Time:</span>
|
|
||||||
<span class="info-value">2.3s</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>Last updated: <span id="timestamp"></span> • ProxMenux Monitor v1.0.0</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Update timestamp
|
|
||||||
document.getElementById('timestamp').textContent = new Date().toLocaleTimeString();
|
|
||||||
|
|
||||||
// Tab functionality
|
|
||||||
document.querySelectorAll('.tab').forEach(tab => {
|
|
||||||
tab.addEventListener('click', function() {
|
|
||||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
||||||
this.classList.add('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-refresh data every 30 seconds
|
|
||||||
setInterval(() => {
|
|
||||||
fetch('/api/system')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
// Update metrics with real data
|
|
||||||
console.log('Updated system data:', data);
|
|
||||||
document.getElementById('timestamp').textContent = new Date().toLocaleTimeString();
|
|
||||||
})
|
|
||||||
.catch(error => console.log('Error fetching data:', error));
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
// PWA Service Worker Registration
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.register('/sw.js');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
'''
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error serving dashboard: {e}")
|
print(f"Error serving dashboard: {e}")
|
||||||
return jsonify({'error': 'Dashboard not available'}), 500
|
return jsonify({'error': f'Dashboard not available: {str(e)}'}), 500
|
||||||
|
|
||||||
@app.route('/manifest.json')
|
@app.route('/manifest.json')
|
||||||
def serve_manifest():
|
def serve_manifest():
|
||||||
"""Serve PWA manifest"""
|
"""Serve PWA manifest"""
|
||||||
return send_from_directory(os.path.join(os.path.dirname(__file__), '..', 'public'), 'manifest.json')
|
try:
|
||||||
|
manifest_paths = [
|
||||||
|
os.path.join(os.path.dirname(__file__), '..', 'web', 'public', 'manifest.json'),
|
||||||
|
os.path.join(os.path.dirname(__file__), '..', 'public', 'manifest.json')
|
||||||
|
]
|
||||||
|
|
||||||
|
for manifest_path in manifest_paths:
|
||||||
|
if os.path.exists(manifest_path):
|
||||||
|
return send_file(manifest_path)
|
||||||
|
|
||||||
|
# Return default manifest if not found
|
||||||
|
return jsonify({
|
||||||
|
"name": "ProxMenux Monitor",
|
||||||
|
"short_name": "ProxMenux",
|
||||||
|
"description": "Proxmox System Monitoring Dashboard",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0a0a0a",
|
||||||
|
"theme_color": "#4f46e5",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/images/proxmenux-logo.png",
|
||||||
|
"sizes": "256x256",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error serving manifest: {e}")
|
||||||
|
return jsonify({}), 404
|
||||||
|
|
||||||
@app.route('/sw.js')
|
@app.route('/sw.js')
|
||||||
def serve_sw():
|
def serve_sw():
|
||||||
@@ -463,19 +126,34 @@ def serve_sw():
|
|||||||
});
|
});
|
||||||
''', 200, {'Content-Type': 'application/javascript'}
|
''', 200, {'Content-Type': 'application/javascript'}
|
||||||
|
|
||||||
|
@app.route('/_next/<path:filename>')
|
||||||
|
def serve_next_static(filename):
|
||||||
|
"""Serve Next.js static files"""
|
||||||
|
try:
|
||||||
|
next_static_dir = os.path.join(os.path.dirname(__file__), '..', 'web', '.next', 'static')
|
||||||
|
if os.path.exists(os.path.join(next_static_dir, filename)):
|
||||||
|
return send_from_directory(next_static_dir, filename)
|
||||||
|
return '', 404
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error serving Next.js static file {filename}: {e}")
|
||||||
|
return '', 404
|
||||||
|
|
||||||
@app.route('/<path:filename>')
|
@app.route('/<path:filename>')
|
||||||
def serve_static_files(filename):
|
def serve_static_files(filename):
|
||||||
"""Serve static files (icons, etc.)"""
|
"""Serve static files (icons, etc.)"""
|
||||||
try:
|
try:
|
||||||
# Try public directory first
|
# Try Next.js public directory first
|
||||||
public_dir = os.path.join(os.path.dirname(__file__), '..', 'public')
|
public_paths = [
|
||||||
if os.path.exists(os.path.join(public_dir, filename)):
|
os.path.join(os.path.dirname(__file__), '..', 'web', 'public'),
|
||||||
return send_from_directory(public_dir, filename)
|
os.path.join(os.path.dirname(__file__), '..', 'public'),
|
||||||
|
os.path.join(os.path.dirname(__file__), '..', 'web', 'out'),
|
||||||
|
os.path.join(os.path.dirname(__file__), '..', 'web', '.next', 'static')
|
||||||
|
]
|
||||||
|
|
||||||
# Try Next.js static directory
|
for public_dir in public_paths:
|
||||||
static_dir = os.path.join(os.path.dirname(__file__), '..', '.next', 'static')
|
file_path = os.path.join(public_dir, filename)
|
||||||
if os.path.exists(os.path.join(static_dir, filename)):
|
if os.path.exists(file_path):
|
||||||
return send_from_directory(static_dir, filename)
|
return send_from_directory(public_dir, filename)
|
||||||
|
|
||||||
return '', 404
|
return '', 404
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -486,12 +164,18 @@ def serve_static_files(filename):
|
|||||||
def serve_images(filename):
|
def serve_images(filename):
|
||||||
"""Serve image files"""
|
"""Serve image files"""
|
||||||
try:
|
try:
|
||||||
web_dir = os.path.join(os.path.dirname(__file__), '..', 'web', 'public', 'images')
|
image_paths = [
|
||||||
if os.path.exists(os.path.join(web_dir, filename)):
|
os.path.join(os.path.dirname(__file__), '..', 'web', 'public', 'images'),
|
||||||
return send_from_directory(web_dir, filename)
|
os.path.join(os.path.dirname(__file__), '..', 'public', 'images'),
|
||||||
else:
|
os.path.dirname(__file__)
|
||||||
# Fallback: try to serve from current directory
|
]
|
||||||
return send_from_directory(os.path.dirname(__file__), filename)
|
|
||||||
|
for image_dir in image_paths:
|
||||||
|
file_path = os.path.join(image_dir, filename)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
return send_from_directory(image_dir, filename)
|
||||||
|
|
||||||
|
return '', 404
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error serving image {filename}: {e}")
|
print(f"Error serving image {filename}: {e}")
|
||||||
return '', 404
|
return '', 404
|
||||||
|
Reference in New Issue
Block a user