diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts
index 53a66150..058080dc 100644
--- a/server/routers/client/listClients.ts
+++ b/server/routers/client/listClients.ts
@@ -119,12 +119,12 @@ const listClientsSchema = z.object({
}),
query: z.string().optional(),
sort_by: z
- .enum(["megabytesIn", "megabytesOut"])
+ .enum(["name", "megabytesIn", "megabytesOut"])
.optional()
.catch(undefined)
.openapi({
type: "string",
- enum: ["megabytesIn", "megabytesOut"],
+ enum: ["name", "megabytesIn", "megabytesOut"],
description: "Field to sort by"
}),
order: z
@@ -363,7 +363,7 @@ export async function listClients(
const countQuery = db.$count(baseQuery.as("filtered_clients"));
const listMachinesQuery = baseQuery
- .limit(page)
+ .limit(pageSize)
.offset(pageSize * (page - 1))
.orderBy(
sort_by
diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts
index a26a5df5..305ba00f 100644
--- a/server/routers/resource/listResources.ts
+++ b/server/routers/resource/listResources.ts
@@ -19,6 +19,7 @@ import {
and,
asc,
count,
+ desc,
eq,
inArray,
isNull,
@@ -63,6 +64,26 @@ const listResourcesSchema = z.object({
description: "Page number to retrieve"
}),
query: z.string().optional(),
+ sort_by: z
+ .enum(["name"])
+ .optional()
+ .catch(undefined)
+ .openapi({
+ type: "string",
+ enum: ["name"],
+ description: "Field to sort by"
+ }),
+ order: z
+ .enum(["asc", "desc"])
+ .optional()
+ .default("asc")
+ .catch("asc")
+ .openapi({
+ type: "string",
+ enum: ["asc", "desc"],
+ default: "asc",
+ description: "Sort order"
+ }),
enabled: z
.enum(["true", "false"])
.transform((v) => v === "true")
@@ -229,8 +250,16 @@ export async function listResources(
)
);
}
- const { page, pageSize, authState, enabled, query, healthStatus } =
- parsedQuery.data;
+ const {
+ page,
+ pageSize,
+ authState,
+ enabled,
+ query,
+ healthStatus,
+ sort_by,
+ order
+ } = parsedQuery.data;
const parsedParams = listResourcesParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
@@ -395,7 +424,13 @@ export async function listResources(
baseQuery
.limit(pageSize)
.offset(pageSize * (page - 1))
- .orderBy(asc(resources.resourceId)),
+ .orderBy(
+ sort_by
+ ? order === "asc"
+ ? asc(resources[sort_by])
+ : desc(resources[sort_by])
+ : asc(resources.resourceId)
+ ),
countQuery
]);
diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts
index 54b207af..307a4c87 100644
--- a/server/routers/site/listSites.ts
+++ b/server/routers/site/listSites.ts
@@ -108,12 +108,12 @@ const listSitesSchema = z.object({
}),
query: z.string().optional(),
sort_by: z
- .enum(["megabytesIn", "megabytesOut"])
+ .enum(["name", "megabytesIn", "megabytesOut"])
.optional()
.catch(undefined)
.openapi({
type: "string",
- enum: ["megabytesIn", "megabytesOut"],
+ enum: ["name", "megabytesIn", "megabytesOut"],
description: "Field to sort by"
}),
order: z
diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts
index a86b4dea..a04f7157 100644
--- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts
+++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts
@@ -4,7 +4,7 @@ import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
import HttpCode from "@server/types/HttpCode";
import type { PaginatedResponse } from "@server/types/Pagination";
-import { and, asc, eq, like, or, sql } from "drizzle-orm";
+import { and, asc, desc, eq, like, or, sql } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
@@ -48,6 +48,26 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({
type: "string",
enum: ["host", "cidr"],
description: "Filter site resources by mode"
+ }),
+ sort_by: z
+ .enum(["name"])
+ .optional()
+ .catch(undefined)
+ .openapi({
+ type: "string",
+ enum: ["name"],
+ description: "Field to sort by"
+ }),
+ order: z
+ .enum(["asc", "desc"])
+ .optional()
+ .default("asc")
+ .catch("asc")
+ .openapi({
+ type: "string",
+ enum: ["asc", "desc"],
+ default: "asc",
+ description: "Sort order"
})
});
@@ -131,7 +151,8 @@ export async function listAllSiteResourcesByOrg(
}
const { orgId } = parsedParams.data;
- const { page, pageSize, query, mode } = parsedQuery.data;
+ const { page, pageSize, query, mode, sort_by, order } =
+ parsedQuery.data;
const conditions = [and(eq(siteResources.orgId, orgId))];
if (query) {
@@ -179,7 +200,13 @@ export async function listAllSiteResourcesByOrg(
baseQuery
.limit(pageSize)
.offset(pageSize * (page - 1))
- .orderBy(asc(siteResources.siteResourceId)),
+ .orderBy(
+ sort_by
+ ? order === "asc"
+ ? asc(siteResources[sort_by])
+ : desc(siteResources[sort_by])
+ : asc(siteResources.siteResourceId)
+ ),
countQuery
]);
diff --git a/server/routers/siteResource/listSiteResources.ts b/server/routers/siteResource/listSiteResources.ts
index 6ecda7c4..5bdf6709 100644
--- a/server/routers/siteResource/listSiteResources.ts
+++ b/server/routers/siteResource/listSiteResources.ts
@@ -5,7 +5,7 @@ import { siteResources, sites, SiteResource } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
-import { eq, and } from "drizzle-orm";
+import { and, asc, desc, eq } from "drizzle-orm";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
@@ -27,7 +27,16 @@ const listSiteResourcesQuerySchema = z.object({
.optional()
.default("0")
.transform(Number)
- .pipe(z.int().nonnegative())
+ .pipe(z.int().nonnegative()),
+ sort_by: z
+ .enum(["name"])
+ .optional()
+ .catch(undefined),
+ order: z
+ .enum(["asc", "desc"])
+ .optional()
+ .default("asc")
+ .catch("asc")
});
export type ListSiteResourcesResponse = {
@@ -75,7 +84,7 @@ export async function listSiteResources(
}
const { siteId, orgId } = parsedParams.data;
- const { limit, offset } = parsedQuery.data;
+ const { limit, offset, sort_by, order } = parsedQuery.data;
// Verify the site exists and belongs to the org
const site = await db
@@ -98,6 +107,13 @@ export async function listSiteResources(
eq(siteResources.orgId, orgId)
)
)
+ .orderBy(
+ sort_by
+ ? order === "asc"
+ ? asc(siteResources[sort_by])
+ : desc(siteResources[sort_by])
+ : asc(siteResources.siteResourceId)
+ )
.limit(limit)
.offset(offset);
diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx
index 68c72b9e..5066f273 100644
--- a/src/components/ClientResourcesTable.tsx
+++ b/src/components/ClientResourcesTable.tsx
@@ -15,7 +15,15 @@ import { InfoPopup } from "@app/components/ui/info-popup";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
-import { ArrowUpDown, ArrowUpRight, MoreHorizontal } from "lucide-react";
+import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn";
+import {
+ ArrowDown01Icon,
+ ArrowUp10Icon,
+ ArrowUpDown,
+ ArrowUpRight,
+ ChevronsUpDownIcon,
+ MoreHorizontal
+} from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
@@ -133,7 +141,26 @@ export default function ClientResourcesTable({
accessorKey: "name",
enableHiding: false,
friendlyName: t("name"),
- header: () => {t("name")}
+ header: () => {
+ const nameOrder = getSortDirection("name", searchParams);
+ const Icon =
+ nameOrder === "asc"
+ ? ArrowDown01Icon
+ : nameOrder === "desc"
+ ? ArrowUp10Icon
+ : ChevronsUpDownIcon;
+
+ return (
+
+ );
+ }
},
{
id: "niceId",
@@ -329,6 +356,14 @@ export default function ClientResourcesTable({
});
}
+ function toggleSort(column: string) {
+ const newSearch = getNextSortOrder(column, searchParams);
+
+ filter({
+ searchParams: newSearch
+ });
+ }
+
const handlePaginationChange = (newPage: PaginationState) => {
searchParams.set("page", (newPage.pageIndex + 1).toString());
searchParams.set("pageSize", newPage.pageSize.toString());
diff --git a/src/components/MachineClientsTable.tsx b/src/components/MachineClientsTable.tsx
index 97de4113..bd5a8e00 100644
--- a/src/components/MachineClientsTable.tsx
+++ b/src/components/MachineClientsTable.tsx
@@ -204,7 +204,26 @@ export default function MachineClientsTable({
accessorKey: "name",
enableHiding: false,
friendlyName: t("name"),
- header: () => {t("name")},
+ header: () => {
+ const nameOrder = getSortDirection("name", searchParams);
+ const Icon =
+ nameOrder === "asc"
+ ? ArrowDown01Icon
+ : nameOrder === "desc"
+ ? ArrowUp10Icon
+ : ChevronsUpDownIcon;
+
+ return (
+
+ );
+ },
cell: ({ row }) => {
const r = row.original;
return (
diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx
index 490904c7..353eddb5 100644
--- a/src/components/ProxyResourcesTable.tsx
+++ b/src/components/ProxyResourcesTable.tsx
@@ -14,15 +14,19 @@ import { InfoPopup } from "@app/components/ui/info-popup";
import { Switch } from "@app/components/ui/switch";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useNavigationContext } from "@app/hooks/useNavigationContext";
+import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { UpdateResourceResponse } from "@server/routers/resource";
import type { PaginationState } from "@tanstack/react-table";
import { AxiosResponse } from "axios";
import {
+ ArrowDown01Icon,
ArrowRight,
+ ArrowUp10Icon,
CheckCircle2,
ChevronDown,
+ ChevronsUpDownIcon,
Clock,
MoreHorizontal,
ShieldCheck,
@@ -318,7 +322,26 @@ export default function ProxyResourcesTable({
accessorKey: "name",
enableHiding: false,
friendlyName: t("name"),
- header: () => {t("name")}
+ header: () => {
+ const nameOrder = getSortDirection("name", searchParams);
+ const Icon =
+ nameOrder === "asc"
+ ? ArrowDown01Icon
+ : nameOrder === "desc"
+ ? ArrowUp10Icon
+ : ChevronsUpDownIcon;
+
+ return (
+
+ );
+ }
},
{
id: "niceId",
@@ -563,6 +586,14 @@ export default function ProxyResourcesTable({
});
}
+ function toggleSort(column: string) {
+ const newSearch = getNextSortOrder(column, searchParams);
+
+ filter({
+ searchParams: newSearch
+ });
+ }
+
const handlePaginationChange = (newPage: PaginationState) => {
searchParams.set("page", (newPage.pageIndex + 1).toString());
searchParams.set("pageSize", newPage.pageSize.toString());
diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx
index c7857773..cc02e5d3 100644
--- a/src/components/SitesTable.tsx
+++ b/src/components/SitesTable.tsx
@@ -141,7 +141,24 @@ export default function SitesTable({
accessorKey: "name",
enableHiding: false,
header: () => {
- return {t("name")};
+ const nameOrder = getSortDirection("name", searchParams);
+ const Icon =
+ nameOrder === "asc"
+ ? ArrowDown01Icon
+ : nameOrder === "desc"
+ ? ArrowUp10Icon
+ : ChevronsUpDownIcon;
+
+ return (
+
+ );
}
},
{