switch to grimmory as book web app and a more modern dashboard
This commit is contained in:
@@ -3,13 +3,14 @@ from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from fastapi import BackgroundTasks, FastAPI, Form, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi import BackgroundTasks, FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from pydantic import BaseModel
|
||||
|
||||
import config
|
||||
import db
|
||||
import grimmory as grimmory_module
|
||||
import sftp as sftp_module
|
||||
import sync
|
||||
|
||||
@@ -50,101 +51,94 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
app = FastAPI(title="CalibreSync", lifespan=lifespan)
|
||||
app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static")
|
||||
templates = Jinja2Templates(directory=Path(__file__).parent / "templates")
|
||||
|
||||
|
||||
# --- Dashboard ---
|
||||
# --- SPA root ---
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def dashboard(request: Request):
|
||||
stats = db.get_stats()
|
||||
runs = [dict(r) for r in db.get_recent_runs(10)]
|
||||
zips = [dict(z) for z in db.get_recent_zips(20)]
|
||||
@app.get("/", include_in_schema=False)
|
||||
async def spa_root():
|
||||
return FileResponse(Path(__file__).parent / "static" / "index.html")
|
||||
|
||||
|
||||
# --- Dashboard data ---
|
||||
|
||||
@app.get("/api/dashboard")
|
||||
async def api_dashboard():
|
||||
interval = int(db.get_setting("scheduler_interval_minutes", "0") or "0")
|
||||
batch_size = int(db.get_setting("sync_batch_size", "0") or "0")
|
||||
cache_info = db.get_remote_cache_info()
|
||||
return templates.TemplateResponse(request, "index.html", {
|
||||
"stats": stats,
|
||||
"runs": runs,
|
||||
"zips": zips,
|
||||
return {
|
||||
"stats": db.get_stats(),
|
||||
"runs": [dict(r) for r in db.get_recent_runs(10)],
|
||||
"zips": [dict(z) for z in db.get_recent_zips(20)],
|
||||
"books": [dict(b) for b in db.get_recent_books(20)],
|
||||
"sync_running": sync.is_running(),
|
||||
"next_run": next_run_time(),
|
||||
"cache_info": db.get_remote_cache_info(),
|
||||
"interval": interval,
|
||||
"batch_size": batch_size,
|
||||
"cache_info": cache_info,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
# --- Settings ---
|
||||
|
||||
@app.get("/settings", response_class=HTMLResponse)
|
||||
async def settings_page(request: Request):
|
||||
@app.get("/api/settings")
|
||||
async def api_get_settings():
|
||||
s = db.get_all_settings()
|
||||
key_pem = s.get("sftp_key", "")
|
||||
return templates.TemplateResponse(request, "settings.html", {
|
||||
"s": s,
|
||||
return {
|
||||
"settings": s,
|
||||
"has_key": bool(key_pem.strip()),
|
||||
"key_fingerprint": sftp_module.get_key_fingerprint(key_pem),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@app.post("/settings")
|
||||
async def save_settings(
|
||||
request: Request,
|
||||
sftp_host: str = Form(""),
|
||||
sftp_port: str = Form("22"),
|
||||
sftp_user: str = Form(""),
|
||||
sftp_auth_method: str = Form("key"),
|
||||
sftp_key: str = Form(""),
|
||||
sftp_password: str = Form(""),
|
||||
sftp_remote_path: str = Form(""),
|
||||
work_dir: str = Form("/tmp/calibresync"),
|
||||
import_dir: str = Form(""),
|
||||
scheduler_interval_minutes: str = Form("0"),
|
||||
sync_batch_size: str = Form("0"),
|
||||
):
|
||||
config.save({
|
||||
"sftp_host": sftp_host,
|
||||
"sftp_port": sftp_port,
|
||||
"sftp_user": sftp_user,
|
||||
"sftp_auth_method": sftp_auth_method,
|
||||
"sftp_key": sftp_key,
|
||||
"sftp_password": sftp_password,
|
||||
"sftp_remote_path": sftp_remote_path,
|
||||
"work_dir": work_dir,
|
||||
"import_dir": import_dir,
|
||||
"scheduler_interval_minutes": scheduler_interval_minutes,
|
||||
"sync_batch_size": sync_batch_size,
|
||||
})
|
||||
class SettingsPayload(BaseModel):
|
||||
sftp_host: str = ""
|
||||
sftp_port: str = "22"
|
||||
sftp_user: str = ""
|
||||
sftp_auth_method: str = "key"
|
||||
sftp_key: str = ""
|
||||
sftp_password: str = ""
|
||||
sftp_remote_path: str = ""
|
||||
grimmory_url: str = ""
|
||||
grimmory_user: str = ""
|
||||
grimmory_password: str = ""
|
||||
grimmory_bookdrop_path: str = ""
|
||||
work_dir: str = "/tmp/calibresync"
|
||||
scheduler_interval_minutes: str = "0"
|
||||
sync_batch_size: str = "0"
|
||||
|
||||
|
||||
@app.post("/api/settings")
|
||||
async def api_save_settings(payload: SettingsPayload):
|
||||
config.save(payload.model_dump())
|
||||
_reschedule_auto_sync()
|
||||
return RedirectResponse("/settings?saved=1", status_code=303)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# --- Sync triggers ---
|
||||
|
||||
@app.post("/sync")
|
||||
async def trigger_sync(background_tasks: BackgroundTasks):
|
||||
@app.post("/api/sync")
|
||||
async def api_trigger_sync(background_tasks: BackgroundTasks):
|
||||
if sync.is_running():
|
||||
return RedirectResponse("/?already_running=1", status_code=303)
|
||||
return {"ok": False, "reason": "already_running"}
|
||||
background_tasks.add_task(sync.run_sync)
|
||||
return RedirectResponse("/?started=1", status_code=303)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.post("/sync/test")
|
||||
async def trigger_test_sync(background_tasks: BackgroundTasks):
|
||||
@app.post("/api/sync/test")
|
||||
async def api_trigger_test_sync(background_tasks: BackgroundTasks):
|
||||
if sync.is_running():
|
||||
return RedirectResponse("/?already_running=1", status_code=303)
|
||||
return {"ok": False, "reason": "already_running"}
|
||||
background_tasks.add_task(sync.run_sync, 1)
|
||||
return RedirectResponse("/?test_started=1", status_code=303)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.post("/sync/rescan")
|
||||
async def trigger_rescan(background_tasks: BackgroundTasks):
|
||||
@app.post("/api/sync/rescan")
|
||||
async def api_trigger_rescan(background_tasks: BackgroundTasks):
|
||||
if sync.is_running():
|
||||
return RedirectResponse("/?already_running=1", status_code=303)
|
||||
return {"ok": False, "reason": "already_running"}
|
||||
cfg = config.load()
|
||||
background_tasks.add_task(sftp_module.refresh_remote_zip_cache, cfg.sftp)
|
||||
return RedirectResponse("/?rescan_started=1", status_code=303)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# --- Connection tests ---
|
||||
@@ -156,13 +150,20 @@ async def test_ssh():
|
||||
return {"ok": ok, "message": message}
|
||||
|
||||
|
||||
@app.get("/api/test/grimmory")
|
||||
async def test_grimmory():
|
||||
cfg = config.load()
|
||||
ok, message = grimmory_module.test_connection(cfg.grimmory.url, cfg.grimmory.user, cfg.grimmory.password)
|
||||
return {"ok": ok, "message": message}
|
||||
|
||||
|
||||
# --- Data reset ---
|
||||
|
||||
@app.post("/settings/reset-sync-data")
|
||||
async def reset_sync_data():
|
||||
@app.post("/api/settings/reset-sync-data")
|
||||
async def api_reset_sync_data():
|
||||
counts = db.clear_sync_data()
|
||||
log.info("Sync data cleared: %s", counts)
|
||||
return RedirectResponse("/settings?reset=1", status_code=303)
|
||||
return {"ok": True, "counts": counts}
|
||||
|
||||
|
||||
# --- JSON status API ---
|
||||
|
||||
Reference in New Issue
Block a user