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:
1 parent
10a1e24420
commit
458092a3c6
3 files changed
+485
-188
No files matched your search
@@ -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)
|
||||
Reference in new issue
Block a user