diff --git a/app/main.py b/app/main.py index baff13a..a14f58d 100644 --- a/app/main.py +++ b/app/main.py @@ -33,7 +33,7 @@ logger = logging.getLogger(__name__) app = FastAPI( title="NetBird MSP Appliance", description="Multi-tenant NetBird management platform for MSPs", - version="1.1.2", + version="1.2.0", docs_url="/api/docs", redoc_url="/api/redoc", openapi_url="/api/openapi.json", diff --git a/app/routers/customers.py b/app/routers/customers.py index 0f1280b..215aa84 100644 --- a/app/routers/customers.py +++ b/app/routers/customers.py @@ -78,22 +78,36 @@ async def create_customer( return response +SORTABLE_CUSTOMER_COLUMNS = { + "id": Customer.id, + "name": Customer.name, + "subdomain": Customer.subdomain, + "status": Customer.status, + "max_devices": Customer.max_devices, + "created_at": Customer.created_at, +} + + @router.get("") async def list_customers( page: int = Query(default=1, ge=1), per_page: int = Query(default=25, ge=1, le=100), search: Optional[str] = Query(default=None), status_filter: Optional[str] = Query(default=None, alias="status"), + sort_by: str = Query(default="id"), + sort_order: str = Query(default="asc", pattern="^(asc|desc)$"), current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): - """List customers with pagination, search, and status filter. + """List customers with pagination, search, status filter, and sorting. Args: page: Page number (1-indexed). per_page: Items per page. search: Search in name, subdomain, email. status_filter: Filter by status. + sort_by: Column to sort by — one of SORTABLE_CUSTOMER_COLUMNS. + sort_order: "asc" or "desc". Returns: Paginated customer list with metadata. @@ -113,8 +127,11 @@ async def list_customers( query = query.filter(Customer.status == status_filter) total = query.count() + + sort_column = SORTABLE_CUSTOMER_COLUMNS.get(sort_by, Customer.id) + sort_expr = sort_column.desc() if sort_order == "desc" else sort_column.asc() customers = ( - query.order_by(Customer.created_at.desc()) + query.order_by(sort_expr, Customer.id.asc()) .offset((page - 1) * per_page) .limit(per_page) .all() diff --git a/static/css/styles.css b/static/css/styles.css index ed991db..f879f93 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -1,5 +1,25 @@ /* NetBird MSP Appliance - Custom Styles */ +/* Sortable table headers */ +.sortable-th { + cursor: pointer; + user-select: none; + white-space: nowrap; +} + +.sortable-th:hover { + color: var(--bs-primary); +} + +.sortable-th .sort-icon { + opacity: 0.35; +} + +.sortable-th.sort-asc .sort-icon, +.sortable-th.sort-desc .sort-icon { + opacity: 1; +} + /* i18n FOUC prevention */ body.i18n-loading #login-page, body.i18n-loading #app-page { diff --git a/static/index.html b/static/index.html index 4fbe6c5..8861e50 100644 --- a/static/index.html +++ b/static/index.html @@ -254,13 +254,13 @@
| ID | -Name | -Subdomain | -Status | +ID | +Name | +Subdomain | +Status | Dashboard | -Devices | -Created | +Devices | +Created | Actions |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ${t('dashboard.noCustomers')} | |||||||||||||