Skip to content

Commit 6c55e3e

Browse files
Performance + fix pages not loading after login without refresh
1 parent d61015d commit 6c55e3e

6 files changed

Lines changed: 169 additions & 69 deletions

File tree

app.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
sys.path.insert(0, os.path.dirname(__file__))
55

66
from flask import Flask
7+
try:
8+
from flask_compress import Compress
9+
_compress_available = True
10+
except ImportError:
11+
_compress_available = False
12+
713
from panel.routes.auth import auth_bp
814
from panel.routes.dashboard import dashboard_bp
915
from panel.routes.websites_core import websites_bp
@@ -45,6 +51,15 @@ def create_app():
4551
app = Flask(__name__, template_folder='web/templates', static_folder='web/static')
4652
app.secret_key = os.environ.get('SECRET_KEY', 'vortex-dev-secret-change-in-prod')
4753

54+
if _compress_available:
55+
app.config['COMPRESS_MIMETYPES'] = [
56+
'text/html', 'application/json', 'application/javascript',
57+
'text/css', 'text/plain',
58+
]
59+
app.config['COMPRESS_LEVEL'] = 6
60+
app.config['COMPRESS_MIN_SIZE'] = 500
61+
Compress(app)
62+
4863
for bp in [auth_bp, dashboard_bp, websites_bp, databases_bp, files_bp,
4964
php_bp, services_bp, firewall_bp, terminal_bp, backups_bp,
5065
dns_bp, mail_bp, ftp_bp, cron_bp, docker_bp, monitoring_bp,

panel/routes/dashboard.py

Lines changed: 105 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,124 @@
11
from flask import Blueprint, jsonify, session
2-
import subprocess, os, re, time
2+
import subprocess, re, time
3+
from concurrent.futures import ThreadPoolExecutor
34

45
dashboard_bp = Blueprint('dashboard', __name__)
56

67
def req():
78
return 'user' in session
89

10+
_stats_cache = {'data': None, 'ts': 0}
11+
_STATS_TTL = 1.5
12+
13+
14+
def _get_stats():
15+
def _proc_stat():
16+
line1 = open('/proc/stat').readline()
17+
v1 = list(map(int, line1.split()[1:]))
18+
time.sleep(0.1)
19+
line2 = open('/proc/stat').readline()
20+
v2 = list(map(int, line2.split()[1:]))
21+
idle_d = (v2[3] + v2[4]) - (v1[3] + v1[4])
22+
total_d = sum(v2) - sum(v1)
23+
return round((1 - idle_d / total_d) * 100, 1) if total_d else 0.0
24+
25+
def _proc_mem():
26+
mem = open('/proc/meminfo').read()
27+
def mi(k):
28+
m = re.search(rf'^{k}:\s+(\d+)', mem, re.M)
29+
return int(m.group(1)) * 1024 if m else 0
30+
total = mi('MemTotal')
31+
return total, total - mi('MemAvailable')
32+
33+
def _disk():
34+
try:
35+
out = subprocess.check_output(
36+
'df / --output=size,used 2>/dev/null | tail -1',
37+
shell=True, text=True, stderr=subprocess.DEVNULL
38+
).split()
39+
return int(out[0]) * 1024, int(out[1]) * 1024
40+
except:
41+
return 0, 0
42+
43+
def _proc_uptime():
44+
try:
45+
sec = int(float(open('/proc/uptime').read().split()[0]))
46+
d, h, m = sec // 86400, (sec % 86400) // 3600, (sec % 3600) // 60
47+
return f"{d}d {h}h {m}m"
48+
except:
49+
return '—'
50+
51+
def _proc_net():
52+
try:
53+
rx = tx = 0
54+
for line in open('/proc/net/dev').readlines()[2:]:
55+
f = line.split()
56+
if f[0].rstrip(':') == 'lo': continue
57+
rx += int(f[1]); tx += int(f[9])
58+
return rx, tx
59+
except:
60+
return 0, 0
61+
62+
def _services():
63+
svcs = ['nginx', 'apache2', 'mysql', 'mariadb',
64+
'php8.5-fpm', 'php8.4-fpm', 'php8.3-fpm', 'php8.2-fpm',
65+
'php8.1-fpm', 'php7.4-fpm',
66+
'redis-server', 'docker', 'fail2ban', 'supervisor']
67+
r = subprocess.run(
68+
'systemctl is-active ' + ' '.join(svcs) + ' 2>/dev/null',
69+
shell=True, capture_output=True, text=True
70+
)
71+
lines = r.stdout.strip().split('\n')
72+
out = {}
73+
for i, svc in enumerate(svcs):
74+
state = lines[i].strip() if i < len(lines) else ''
75+
if state in ('active', 'inactive', 'failed'): out[svc] = state
76+
return {k: v for k, v in out.items() if v}
77+
78+
cpu_result = [0.0]; disk_result = [(0,0)]; svc_result = [{}]
79+
def _get_cpu(): cpu_result[0] = _proc_stat()
80+
def _get_disk(): disk_result[0] = _disk()
81+
def _get_svcs(): svc_result[0] = _services()
82+
83+
with ThreadPoolExecutor(max_workers=3) as ex:
84+
for f in [ex.submit(_get_cpu), ex.submit(_get_disk), ex.submit(_get_svcs)]:
85+
f.result()
86+
87+
ram_total, ram_used = _proc_mem()
88+
disk_total, disk_used = disk_result[0]
89+
rx, tx = _proc_net()
90+
return {
91+
'ok': True, 'cpu': cpu_result[0],
92+
'ram': {'used': ram_used, 'total': ram_total},
93+
'disk': {'used': disk_used, 'total': disk_total},
94+
'load': open('/proc/loadavg').read().split()[:3],
95+
'uptime': _proc_uptime(),
96+
'services': svc_result[0],
97+
'net': {'rx': rx, 'tx': tx},
98+
}
99+
100+
9101
@dashboard_bp.route('/api/dashboard/stats')
10102
def stats():
11-
if not req(): return jsonify({'ok':False}),401
12-
def sh(c):
13-
try: return subprocess.check_output(c,shell=True,text=True,stderr=subprocess.DEVNULL).strip()
14-
except: return ''
15-
16-
# CPU
17-
cpu_idle = sh("top -bn1 | grep 'Cpu(s)' | awk '{print $8}' | tr -d '%id,'")
18-
try: cpu = round(100 - float(cpu_idle.split()[0] if cpu_idle else '0'), 1)
19-
except: cpu = 0.0
20-
21-
# RAM
22-
mem = sh("free -b | awk '/^Mem/{print $2,$3}'").split()
23-
ram_total = int(mem[0]) if len(mem)>0 else 0
24-
ram_used = int(mem[1]) if len(mem)>1 else 0
25-
26-
# Disk
27-
disk = sh("df / | awk 'NR==2{print $2,$3}'").split()
28-
disk_total = int(disk[0])*1024 if len(disk)>0 else 0
29-
disk_used = int(disk[1])*1024 if len(disk)>1 else 0
30-
31-
# Load
32-
load = sh("cat /proc/loadavg").split()[:3]
33-
uptime_sec = sh("cat /proc/uptime").split()[0]
34-
try: uptime_sec = int(float(uptime_sec))
35-
except: uptime_sec = 0
36-
days = uptime_sec//86400; hours=(uptime_sec%86400)//3600; mins=(uptime_sec%3600)//60
37-
uptime_str = f"{days}d {hours}h {mins}m"
38-
39-
# Services
40-
services = {}
41-
for svc in ['nginx','apache2','mysql','mariadb','php8.3-fpm','php8.2-fpm','redis-server','docker']:
42-
s = sh(f"systemctl is-active {svc} 2>/dev/null")
43-
if s: services[svc] = s
44-
45-
# Network
46-
net = sh("cat /proc/net/dev | awk 'NR>2{rx+=$2;tx+=$10}END{print rx,tx}'").split()
47-
net_rx = int(net[0]) if net else 0
48-
net_tx = int(net[1]) if net else 0
49-
50-
return jsonify({
51-
'ok': True,
52-
'cpu': cpu,
53-
'ram': {'used': ram_used, 'total': ram_total},
54-
'disk': {'used': disk_used, 'total': disk_total},
55-
'load': load,
56-
'uptime': uptime_str,
57-
'services': services,
58-
'net': {'rx': net_rx, 'tx': net_tx},
59-
})
103+
if not req(): return jsonify({'ok': False}), 401
104+
now = time.monotonic()
105+
if _stats_cache['data'] is None or (now - _stats_cache['ts']) > _STATS_TTL:
106+
_stats_cache['data'] = _get_stats()
107+
_stats_cache['ts'] = now
108+
return jsonify(_stats_cache['data'])
109+
60110

61111
@dashboard_bp.route('/api/dashboard/history')
62112
def history():
63-
if not req(): return jsonify({'ok':False}),401
113+
if not req(): return jsonify({'ok': False}), 401
64114
import random, math
65-
# Generate realistic-looking history data (replace with real metrics if psutil installed)
66115
now = int(time.time())
67116
points = []
68117
for i in range(30):
69-
t = now - (29-i)*60
118+
t = now - (29 - i) * 60
70119
points.append({
71120
'time': t,
72-
'cpu': round(15 + 25*abs(math.sin(i*0.4)) + random.uniform(-3,3), 1),
73-
'ram': round(45 + 15*abs(math.sin(i*0.3)) + random.uniform(-2,2), 1),
121+
'cpu': round(15 + 25*abs(math.sin(i*0.4)) + random.uniform(-3,3), 1),
122+
'ram': round(45 + 15*abs(math.sin(i*0.3)) + random.uniform(-2,2), 1),
74123
})
75-
return jsonify({'ok':True,'history':points})
124+
return jsonify({'ok': True, 'history': points})

panel/routes/modules.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from flask import Blueprint, jsonify, request, session, Response
22
import subprocess, os, threading, time, json, uuid, re
33
try:
4-
from panel.routes.os_utils import get_os, pkg_install, pkg_update, nginx_install_script, php_install_script, mariadb_install_script, postgresql_install_script, redis_install_script, mongodb_install_script, docker_install_script, nodejs_install_script
4+
from panel.routes.os_utils import get_os, pkg_install, pkg_update, nginx_install_script, php_install_script, mariadb_install_script, postgresql_install_script, redis_install_script, mongodb_install_script, docker_install_script, nodejs_install_script, panel_cache
55
except ImportError:
6-
from os_utils import get_os, pkg_install, pkg_update, nginx_install_script, php_install_script, mariadb_install_script, postgresql_install_script, redis_install_script, mongodb_install_script, docker_install_script, nodejs_install_script
6+
from os_utils import get_os, pkg_install, pkg_update, nginx_install_script, php_install_script, mariadb_install_script, postgresql_install_script, redis_install_script, mongodb_install_script, docker_install_script, nodejs_install_script, panel_cache
77

88
modules_bp = Blueprint('modules', __name__)
99

@@ -740,6 +740,8 @@ def get_conflict(mod_id):
740740
@modules_bp.route('/api/modules')
741741
def list_modules():
742742
if not req(): return jsonify({'ok':False}), 401
743+
cached = panel_cache.get('modules_list')
744+
if cached: return jsonify(cached)
743745
result = []
744746
for m in MODULES:
745747
installed = is_installed(m['check'])
@@ -762,7 +764,9 @@ def list_modules():
762764
'builtin': m.get('builtin', False),
763765
'conflict_group': next((g for g,ms in CONFLICT_GROUPS.items() if m['id'] in ms), None),
764766
})
765-
return jsonify({'ok':True, 'modules':result})
767+
response = {'ok':True, 'modules':result}
768+
panel_cache.set('modules_list', response, ttl=30)
769+
return jsonify(response)
766770

767771
@modules_bp.route('/api/modules/<mod_id>/install', methods=['POST'])
768772
def install_module(mod_id):
@@ -863,6 +867,7 @@ def run_job():
863867
_jobs[job_id]['lines'].append(
864868
f'[VortexPanel] {"✓ Installed successfully! Version: "+inst_ver if installed else "⚠ Installation may have failed — check output above."}'
865869
)
870+
panel_cache.invalidate('modules_list')
866871

867872
threading.Thread(target=run_job, daemon=True).start()
868873
return jsonify({'ok':True, 'job_id':job_id, 'action':'install'})
@@ -911,6 +916,7 @@ def run_job():
911916
_jobs[job_id]['lines'].append(
912917
f'[VortexPanel] {"✓ Removed successfully!" if removed else "⚠ May not be fully removed — check output above."}'
913918
)
919+
panel_cache.invalidate('modules_list')
914920

915921
threading.Thread(target=run_job, daemon=True).start()
916922
return jsonify({'ok':True, 'job_id':job_id, 'action':'uninstall'})

panel/routes/os_utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ def pkg_install(packages, extra_flags=''):
6262
return f'{pkg} install -y {extra_flags} {packages}'
6363
return f'apt-get install -y {packages}'
6464

65+
import time as _time
66+
67+
class _TTLCache:
68+
"""Simple in-process TTL cache for expensive read-only endpoints."""
69+
def __init__(self):
70+
self._store = {}
71+
def get(self, key):
72+
item = self._store.get(key)
73+
if item and (_time.monotonic() - item['ts']) < item['ttl']:
74+
return item['val']
75+
return None
76+
def set(self, key, val, ttl=30):
77+
self._store[key] = {'val': val, 'ts': _time.monotonic(), 'ttl': ttl}
78+
def invalidate(self, key):
79+
self._store.pop(key, None)
80+
81+
panel_cache = _TTLCache()
82+
83+
6584
def pkg_update():
6685
"""Return update command for current OS"""
6786
os_info = get_os()

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ flask-sock
44
requests
55
gunicorn
66
boto3
7+
flask-compress

0 commit comments

Comments
 (0)