diff --git a/app/db/apps.py b/app/db/apps.py index 298c309..df3110f 100644 --- a/app/db/apps.py +++ b/app/db/apps.py @@ -81,14 +81,11 @@ def update_app_resources(app_id: str, memory: str, cpus: str): con.close() -def get_deployments( - limit: int = 100, +def build_deployment_filters( status: str | None = None, app_id: str | None = None, source: str | None = None, ): - con = get_connection() - filters = [] params = [] @@ -116,6 +113,19 @@ def get_deployments( params.append(source) where = f"WHERE {' AND '.join(filters)}" if filters else "" + return where, params + + +def get_deployments( + limit: int = 20, + offset: int = 0, + status: str | None = None, + app_id: str | None = None, + source: str | None = None, +): + con = get_connection() + where, params = build_deployment_filters(status=status, app_id=app_id, source=source) + rows = con.execute( f""" SELECT @@ -138,14 +148,36 @@ def get_deployments( END, id DESC LIMIT ? + OFFSET ? """, - (*params, *RUNNING_STATUSES, limit), + (*params, *RUNNING_STATUSES, limit, offset), ).fetchall() con.close() return [dict(row) for row in rows] +def count_deployments( + status: str | None = None, + app_id: str | None = None, + source: str | None = None, +): + con = get_connection() + where, params = build_deployment_filters(status=status, app_id=app_id, source=source) + + row = con.execute( + f""" + SELECT COUNT(*) AS count + FROM deployments + {where} + """, + params, + ).fetchone() + + con.close() + return row["count"] if row else 0 + + def get_deployment_filter_options(): con = get_connection() diff --git a/app/routes/deployments.py b/app/routes/deployments.py index 1ea3e15..76f9075 100644 --- a/app/routes/deployments.py +++ b/app/routes/deployments.py @@ -1,12 +1,14 @@ import html +import math from datetime import datetime -from urllib.parse import quote +from urllib.parse import quote, urlencode from fastapi import APIRouter, Depends, HTTPException, Query, Request from fastapi.responses import HTMLResponse, PlainTextResponse from app.auth import require_user from app.db.apps import ( + count_deployments, get_deployment, get_deployment_filter_options, get_deployment_stats, @@ -19,6 +21,7 @@ router = APIRouter() SUCCESS_STATUSES = {"ok", "success", "succeeded", "done", "deployed", "completed"} RUNNING_STATUSES = {"running", "pending", "queued", "in_progress", "starting"} FAILED_STATUSES = {"failed", "failure", "error", "cancelled", "canceled"} +DEFAULT_PAGE_SIZE = 20 def normalize_status(status: str | None) -> str: @@ -115,18 +118,43 @@ def render_options(values: list[str], selected: str | None, empty_label: str) -> return "".join(options) +def pagination_url(page_number: int, status: str, app_id: str, source: str) -> str: + params = {"page": page_number} + if status: + params["status"] = status + if app_id: + params["app_id"] = app_id + if source: + params["source"] = source + return f"/portal/deployments?{urlencode(params)}" + + @router.get("/deployments", response_class=HTMLResponse) async def deployments_page( request: Request, status: str = Query(""), app_id: str = Query(""), source: str = Query(""), + page_number: int = Query(1, alias="page", ge=1), user=Depends(require_user), ): selected_status = status.strip() selected_app = app_id.strip() selected_source = source.strip() + offset = (page_number - 1) * DEFAULT_PAGE_SIZE + total_deployments = count_deployments( + status=selected_status or None, + app_id=selected_app or None, + source=selected_source or None, + ) + total_pages = max(1, math.ceil(total_deployments / DEFAULT_PAGE_SIZE)) + if page_number > total_pages: + page_number = total_pages + offset = (page_number - 1) * DEFAULT_PAGE_SIZE + deployments = get_deployments( + limit=DEFAULT_PAGE_SIZE, + offset=offset, status=selected_status or None, app_id=selected_app or None, source=selected_source or None, @@ -179,6 +207,22 @@ async def deployments_page( status_options = render_options(status_values, selected_status, "Všechny statusy") app_options = render_options(options["apps"], selected_app, "Všechny aplikace") source_options = render_options(options["sources"], selected_source, "Všechny zdroje") + first_item = offset + 1 if total_deployments else 0 + last_item = min(offset + len(deployments), total_deployments) + previous_disabled = " disabled" if page_number <= 1 else "" + next_disabled = " disabled" if page_number >= total_pages else "" + previous_href = pagination_url(max(1, page_number - 1), selected_status, selected_app, selected_source) + next_href = pagination_url(min(total_pages, page_number + 1), selected_status, selected_app, selected_source) + pagination = f""" +
| ID | @@ -224,6 +270,7 @@ async def deployments_page(
|---|