Feat: adds a login page

This commit is contained in:
Etienne Chassaing 2025-07-09 15:23:06 +02:00
parent f84095fc20
commit 4d4d0f986f
4 changed files with 102 additions and 35 deletions

4
credentials.json Normal file
View File

@ -0,0 +1,4 @@
{
"username": "nehemis",
"password": "password"
}

110
main.py
View File

@ -1,46 +1,99 @@
from fastapi import FastAPI, HTTPException, Query from fastapi import FastAPI, HTTPException, Query, Form, Depends
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse, RedirectResponse
import logging import logging
import os import os
from fastapi import Request, APIRouter from fastapi import Request, APIRouter
import subprocess
import platform import platform
from fastapi.templating import Jinja2Templates # pip install fastapi uvicorn jinja2 python-multipart passlib
from starlette.middleware.sessions import SessionMiddleware
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.status import HTTP_302_FOUND
import json
from pathlib import Path
if platform.system() in ['Darwin']: # macOS or Windows if platform.system() in ['Darwin']: # macOS or Windows
from MockCAN import CANBackend from MockCAN import CANBackend
else : else :
from classCAN import CANBackend # Your real backend from classCAN import CANBackend # Your real backend
app = FastAPI() app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="your_super_secret_key")
router = APIRouter() router = APIRouter()
templates = Jinja2Templates(directory="templates")
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
can_backend = CANBackend()
# Mount static files
app.mount("/static", StaticFiles(directory="static"), name="static")
can_backend = CANBackend() can_backend = CANBackend()
# Serve static files (HTML, JS, CSS) # Serve static files (HTML, JS, CSS)
app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/static", StaticFiles(directory="static"), name="static")
# Jinja2 templates
templates = Jinja2Templates(directory="templates")
# @router.post("/webhook") # CREDENTIALS
# async def github_webhook(request: Request):
# payload = await request.json()
# print("[WEBHOOK] Received webhook:", payload.get("head_commit", {}).get("message"))
# try: # Load users from JSON file at startup
# # Call the update script on the HOST using host bash CREDENTIAL_PATH = Path("credentials.json")
# subprocess.run( if CREDENTIAL_PATH.exists():
# ["/usr/bin/bash", "-c", "bash /home/hmi/Desktop/HMI/update_hmi.sh"], with CREDENTIAL_PATH.open("r") as f:
# check=True, CREDENTIALS = json.load(f)
# capture_output=True, else:
# text=True CREDENTIALS = {}
# )
# return {"status": "Update triggered"} USERNAME = CREDENTIALS["username"]
# except subprocess.CalledProcessError as e: PASSWORD = CREDENTIALS["password"]
# print(f"[WEBHOOK] Update failed:\n{e.stderr}")
# return {"status": "Update failed", "error": str(e)}
# ======== LOGIN & SESSION HANDLING ========
def require_login(request: Request):
user = request.session.get("user")
if user != USERNAME:
# raise 302 to trigger redirection manually (FastAPI doesn't support redirects from Depends directly)
raise StarletteHTTPException(status_code=302, detail="Redirect to login")
return user
@app.get("/", response_class=HTMLResponse)
def login_form(request: Request):
return templates.TemplateResponse("login.html", {"request": request})
@app.post("/login")
def login(request: Request, username: str = Form(...), password: str = Form(...)):
if username == USERNAME and password == PASSWORD:
request.session["user"] = username
return RedirectResponse("/control", status_code=HTTP_302_FOUND)
return templates.TemplateResponse("login.html", {"request": request, "error": "Invalid credentials.json"})
@app.get("/logout")
def logout(request: Request):
request.session.clear()
return RedirectResponse("/", status_code=HTTP_302_FOUND)
# ======== PROTECTED INTERFACE ========
@app.get("/control", response_class=HTMLResponse)
def control_page(request: Request):
if request.session.get("user") != USERNAME:
return RedirectResponse("/", status_code=HTTP_302_FOUND)
return templates.TemplateResponse("control.html", {"request": request})
@app.get("/monitor-page", response_class=HTMLResponse)
def monitor_page(request: Request):
if request.session.get("user") != USERNAME:
return RedirectResponse("/", status_code=HTTP_302_FOUND)
with open("static/monitor.html") as f:
return HTMLResponse(f.read())
# ======== CAN + BACKEND ROUTES ========
@app.post("/connect_toggle") @app.post("/connect_toggle")
def connect_toggle(): def connect_toggle():
"""Toggle CAN connection.""" """Toggle CAN connection."""
@ -147,19 +200,6 @@ def can_status():
"""Return current CAN connection status.""" """Return current CAN connection status."""
return {"connected": can_backend.connected} return {"connected": can_backend.connected}
@app.get("/", response_class=HTMLResponse)
async def read_root():
"""Serve main HTML page."""
with open("static/index.html", "r") as file:
return HTMLResponse(content=file.read(), status_code=200)
@app.get("/monitor-page", response_class=HTMLResponse)
async def read_monitor_page():
"""Serve monitor HTML page."""
with open("static/monitor.html", "r") as file:
return HTMLResponse(content=file.read(), status_code=200)
app.include_router(router) app.include_router(router)

23
templates/login.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<style>
body { font-family: sans-serif; background-color: #121212; color: white; display: flex; justify-content: center; align-items: center; height: 100vh; }
form { background: #1e1e1e; padding: 30px; border-radius: 8px; box-shadow: 0 0 10px black; }
input { display: block; margin: 10px 0; padding: 10px; width: 100%; }
button { padding: 10px; width: 100%; background: #4285F4; color: white; border: none; border-radius: 5px; }
</style>
</head>
<body>
<form method="post" action="/login">
<h2>Login</h2>
{% if error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
<input type="text" name="username" placeholder="Username" required />
<input type="password" name="password" placeholder="Password" required />
<button type="submit">Log In</button>
</form>
</body>
</html>