Add Phase 2 dashboard + fix Phase 1 issues

- Implement parse_smb(), remove shell=True, add input validation, HTTP Basic Auth (Phase 1)
- Add /api/dashboard with daemon status, disk usage, per-share du, quota usage (Phase 2)
- Rewrite index.html with Dashboard as landing tab, progress bars, XSS-safe rendering
- Update README with full LXC install guide including git clone and update steps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Sonnet 4.6 committed 2026-06-19 21:58:47 +02:00
1 parent 10a1e24420
commit 458092a3c6
3 files changed
+485 -188

No files matched your search

+102
View File
@@ -1,5 +1,6 @@
import os
import re
import shutil
import subprocess
import functools
from pathlib import Path
@@ -219,5 +220,106 @@ def set_quota():
return jsonify({'status': 'ok'})
# ===== DASHBOARD =====
def _daemon_status():
def is_active(service):
r = subprocess.run(['systemctl', 'is-active', service], capture_output=True, text=True)
return r.stdout.strip() == 'active'
return {'smbd': is_active('smbd'), 'nfsd': is_active('nfs-server')}
def _disk_usage():
try:
u = shutil.disk_usage('/srv')
return {
'path': '/srv',
'total_gb': round(u.total / 1024**3, 1),
'used_gb': round(u.used / 1024**3, 1),
'free_gb': round(u.free / 1024**3, 1),
'percent': round(u.used / u.total * 100, 1) if u.total else 0,
}
except Exception as e:
return {'error': str(e)}
def _du_bytes(path):
try:
r = subprocess.run(['du', '-sb', path], capture_output=True, text=True, timeout=15)
if r.returncode == 0:
parts = r.stdout.split()
return int(parts[0]) if parts else 0
except Exception:
pass
return 0
def _shares_with_usage():
result = []
for s in parse_smb():
result.append({
'type': 'smb',
'name': s['name'],
'path': s['path'],
'access': s['valid_users'],
'size_gb': round(_du_bytes(s['path']) / 1024**3, 2) if s['path'] else 0,
})
for s in get_nfs_shares():
result.append({
'type': 'nfs',
'name': None,
'path': s['path'],
'access': s['clients'],
'size_gb': round(_du_bytes(s['path']) / 1024**3, 2) if s['path'] else 0,
})
return result
def _quota_usage():
try:
r = subprocess.run(['repquota', '-u', '/srv'], capture_output=True, text=True, timeout=10)
except FileNotFoundError:
return []
quotas = []
past_sep = False
for line in r.stdout.splitlines():
if line.startswith('---'):
past_sep = True
continue
if not past_sep:
continue
parts = line.split()
if len(parts) < 5 or not re.match(r'^[+-]{2}$', parts[1]):
continue
try:
used_kb = int(parts[2])
soft_kb = int(parts[3])
hard_kb = int(parts[4])
except (ValueError, IndexError):
continue
if soft_kb == 0 and hard_kb == 0 and used_kb == 0:
continue
quotas.append({
'user': parts[0],
'used_gb': round(used_kb / 1024**2, 2),
'soft_gb': round(soft_kb / 1024**2, 2),
'hard_gb': round(hard_kb / 1024**2, 2),
'percent': round(used_kb / soft_kb * 100, 1) if soft_kb else 0,
})
return quotas
@app.route('/api/dashboard', methods=['GET'])
@require_auth
def get_dashboard():
return jsonify({
'status': _daemon_status(),
'disk': _disk_usage(),
'shares': _shares_with_usage(),
'quotas': _quota_usage(),
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)