117 lines
3.3 KiB
Python
117 lines
3.3 KiB
Python
from flask import Flask, render_template, jsonify, request, Response
|
||
import requests
|
||
import os
|
||
from functools import wraps
|
||
from requests.auth import HTTPBasicAuth
|
||
|
||
app = Flask(__name__)
|
||
|
||
WOL_API_URL = os.getenv("WOL_API_URL", "https://wol.zea.lt")
|
||
WOL_API_USER = os.getenv("WOL_API_USER")
|
||
WOL_API_PASS = os.getenv("WOL_API_PASS")
|
||
WEATHER_API_KEY = os.getenv("WEATHER_API_KEY")
|
||
WEATHER_CITY = os.getenv("WEATHER_CITY", "")
|
||
|
||
|
||
def _fetch_wol_status():
|
||
"""Fetch device list and status from WOL API (single source of truth)."""
|
||
try:
|
||
auth = (
|
||
HTTPBasicAuth(WOL_API_USER, WOL_API_PASS)
|
||
if WOL_API_USER and WOL_API_PASS
|
||
else None
|
||
)
|
||
r = requests.get(
|
||
f"{WOL_API_URL}/status",
|
||
auth=auth,
|
||
timeout=5,
|
||
)
|
||
if r.ok:
|
||
return r.json().get("devices", {})
|
||
except requests.RequestException:
|
||
pass
|
||
return {}
|
||
|
||
|
||
def check_auth(username, password):
|
||
return username == WOL_API_USER and password == WOL_API_PASS
|
||
|
||
|
||
def authenticate():
|
||
"""Sends a 401 response that enables basic auth."""
|
||
return Response(
|
||
"Could not verify your access level for that URL.\n"
|
||
"You have to login with proper credentials",
|
||
401,
|
||
{"WWW-Authenticate": 'Basic realm="Login Required"'},
|
||
)
|
||
|
||
|
||
def requires_auth(f):
|
||
@wraps(f)
|
||
def decorated(*args, **kwargs):
|
||
auth = request.authorization
|
||
if not auth or not check_auth(auth.username, auth.password):
|
||
return authenticate()
|
||
return f(*args, **kwargs)
|
||
|
||
return decorated
|
||
|
||
|
||
@app.route("/")
|
||
def dashboard():
|
||
devices_data = _fetch_wol_status()
|
||
devices = {k: v.get("mac", "") for k, v in devices_data.items()}
|
||
status = {k: v.get("online", False) for k, v in devices_data.items()}
|
||
return render_template(
|
||
"dashboard.html",
|
||
devices=devices,
|
||
status=status,
|
||
)
|
||
|
||
|
||
@app.route("/wake/<device>", methods=["POST"])
|
||
@requires_auth
|
||
def wake_device(device):
|
||
devices_data = _fetch_wol_status()
|
||
if device not in devices_data:
|
||
return jsonify({"error": "Device not found"}), 404
|
||
try:
|
||
auth = HTTPBasicAuth(WOL_API_USER, WOL_API_PASS)
|
||
r = requests.post(
|
||
f"{WOL_API_URL}/wake/{device}",
|
||
auth=auth,
|
||
timeout=5,
|
||
)
|
||
if r.ok:
|
||
return jsonify({"message": f"Wake command sent to {device}"})
|
||
return jsonify({"error": f"Failed to send wake command: {r.text}"}), r.status_code
|
||
except requests.RequestException as e:
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
@app.route("/weather")
|
||
def weather():
|
||
"""Proxy for OpenWeatherMap – keeps API key server-side."""
|
||
if not WEATHER_API_KEY or not WEATHER_CITY:
|
||
return jsonify({"error": "Weather not configured"}), 503
|
||
try:
|
||
r = requests.get(
|
||
"https://api.openweathermap.org/data/2.5/weather",
|
||
params={
|
||
"q": WEATHER_CITY,
|
||
"appid": WEATHER_API_KEY,
|
||
"units": "metric",
|
||
},
|
||
timeout=5,
|
||
)
|
||
if r.ok:
|
||
return jsonify(r.json())
|
||
except requests.RequestException:
|
||
pass
|
||
return jsonify({"error": "Weather unavailable"}), 503
|
||
|
||
|
||
if __name__ == "__main__":
|
||
app.run(host="0.0.0.0", port=8080)
|