GUI verbessert und Admin console eingebaut
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
import http.server
|
||||
import json
|
||||
import shutil
|
||||
import socketserver
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
PORT = 8082
|
||||
EXOMY_CONTAINER = 'exomy_autostart'
|
||||
|
||||
|
||||
class ReusableThreadingTCPServer(socketserver.ThreadingTCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
|
||||
def run_command(command):
|
||||
result = subprocess.run(command, capture_output=True, text=True, check=False)
|
||||
return result.stdout.strip(), result.stderr.strip(), result.returncode
|
||||
|
||||
|
||||
def read_cpu_temperature():
|
||||
try:
|
||||
with open('/sys/class/thermal/thermal_zone0/temp', 'r', encoding='utf-8') as handle:
|
||||
raw_value = handle.read().strip()
|
||||
return f'{int(raw_value) / 1000:.1f} °C'
|
||||
except (FileNotFoundError, ValueError):
|
||||
return 'unbekannt'
|
||||
|
||||
|
||||
def format_uptime():
|
||||
try:
|
||||
with open('/proc/uptime', 'r', encoding='utf-8') as handle:
|
||||
uptime_seconds = int(float(handle.read().split()[0]))
|
||||
except (FileNotFoundError, ValueError, IndexError):
|
||||
return 'unbekannt'
|
||||
|
||||
days, remainder = divmod(uptime_seconds, 86400)
|
||||
hours, remainder = divmod(remainder, 3600)
|
||||
minutes, _ = divmod(remainder, 60)
|
||||
|
||||
parts = []
|
||||
if days:
|
||||
parts.append(f'{days}d')
|
||||
if days or hours:
|
||||
parts.append(f'{hours}h')
|
||||
parts.append(f'{minutes}m')
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
def format_disk_free():
|
||||
usage = shutil.disk_usage('/')
|
||||
free_gb = usage.free / (1024 ** 3)
|
||||
total_gb = usage.total / (1024 ** 3)
|
||||
return f'{free_gb:.1f} GB frei / {total_gb:.1f} GB'
|
||||
|
||||
|
||||
def get_ip_addresses():
|
||||
stdout, _, returncode = run_command(['hostname', '-I'])
|
||||
if returncode != 0 or not stdout:
|
||||
return 'unbekannt'
|
||||
ips = [item for item in stdout.split() if item]
|
||||
return ', '.join(ips) if ips else 'unbekannt'
|
||||
|
||||
|
||||
def get_wifi_status():
|
||||
stdout, _, returncode = run_command(['nmcli', '-t', '-f', 'DEVICE,STATE,CONNECTION', 'device'])
|
||||
if returncode != 0 or not stdout:
|
||||
return 'unbekannt'
|
||||
|
||||
for line in stdout.splitlines():
|
||||
parts = line.split(':', 2)
|
||||
if len(parts) != 3:
|
||||
continue
|
||||
device, state, connection = parts
|
||||
if device == 'wlan0':
|
||||
if state == 'connected':
|
||||
return f'verbunden: {connection}'
|
||||
return state
|
||||
|
||||
return 'kein wlan0'
|
||||
|
||||
|
||||
def get_systemd_state(service_name):
|
||||
stdout, _, returncode = run_command(['systemctl', 'is-active', service_name])
|
||||
if returncode == 0 and stdout:
|
||||
return stdout
|
||||
return stdout or 'unknown'
|
||||
|
||||
|
||||
def get_container_state(container_name):
|
||||
stdout, _, returncode = run_command(['docker', 'inspect', '-f', '{{.State.Status}}', container_name])
|
||||
if returncode == 0 and stdout:
|
||||
return stdout
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def collect_status():
|
||||
return {
|
||||
'status': 'Bereit',
|
||||
'system': {
|
||||
'wifi': get_wifi_status(),
|
||||
'ips': get_ip_addresses(),
|
||||
'cpu_temperature': read_cpu_temperature(),
|
||||
'uptime': format_uptime(),
|
||||
'disk_free': format_disk_free(),
|
||||
},
|
||||
'services': {
|
||||
'camera': get_systemd_state('exomy-camera-stream.service'),
|
||||
'admin_api': get_systemd_state('exomy-admin-api.service'),
|
||||
'exomy': get_container_state(EXOMY_CONTAINER),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def restart_camera_service():
|
||||
subprocess.Popen(['systemctl', 'restart', 'exomy-camera-stream.service'])
|
||||
|
||||
|
||||
def restart_exomy_container():
|
||||
subprocess.Popen(['docker', 'restart', EXOMY_CONTAINER])
|
||||
|
||||
|
||||
def reboot_raspberry_pi():
|
||||
subprocess.Popen(['systemctl', 'reboot'])
|
||||
|
||||
|
||||
def shutdown_raspberry_pi():
|
||||
subprocess.Popen(['systemctl', 'poweroff'])
|
||||
|
||||
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def end_headers(self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
super().end_headers()
|
||||
|
||||
def write_json(self, status_code, payload):
|
||||
body = json.dumps(payload).encode('utf-8')
|
||||
self.send_response(status_code)
|
||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||
self.send_header('Content-Length', str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(204)
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/api/status':
|
||||
self.write_json(200, collect_status())
|
||||
return
|
||||
self.write_json(404, {'status': 'Unbekannt'})
|
||||
|
||||
def do_POST(self):
|
||||
actions = {
|
||||
'/api/restart-camera': {
|
||||
'handler': restart_camera_service,
|
||||
'status': 'Kameradienst wird neu gestartet'
|
||||
},
|
||||
'/api/restart-container': {
|
||||
'handler': restart_exomy_container,
|
||||
'status': 'ExoMy-Container wird neu gestartet'
|
||||
},
|
||||
'/api/reboot': {
|
||||
'handler': reboot_raspberry_pi,
|
||||
'status': 'Raspberry Pi wird neu gestartet'
|
||||
},
|
||||
'/api/shutdown': {
|
||||
'handler': shutdown_raspberry_pi,
|
||||
'status': 'Raspberry Pi wird heruntergefahren'
|
||||
}
|
||||
}
|
||||
|
||||
action = actions.get(self.path)
|
||||
if action is None:
|
||||
self.write_json(404, {'status': 'Unbekannt'})
|
||||
return
|
||||
|
||||
self.write_json(202, {'status': action['status']})
|
||||
self.wfile.flush()
|
||||
time.sleep(0.1)
|
||||
action['handler']()
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
with ReusableThreadingTCPServer(('0.0.0.0', PORT), Handler) as server:
|
||||
server.daemon_threads = True
|
||||
server.serve_forever()
|
||||
Reference in New Issue
Block a user