mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-10 14:38:22 +02:00
✨ label filter column for private resources
This commit is contained in:
@@ -85,7 +85,27 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({
|
|||||||
type: "integer",
|
type: "integer",
|
||||||
description:
|
description:
|
||||||
"When set, only site resources associated with this site (via network) are returned"
|
"When set, only site resources associated with this site (via network) are returned"
|
||||||
})
|
}),
|
||||||
|
labels: z
|
||||||
|
.preprocess((val) => {
|
||||||
|
if (val === undefined || val === null || val === "") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
// the array is returned as this
|
||||||
|
if (typeof val === "string") {
|
||||||
|
return val.split(",");
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, z.array(z.string()))
|
||||||
|
.optional()
|
||||||
|
.catch([])
|
||||||
|
.openapi({
|
||||||
|
type: "array",
|
||||||
|
description: "Filter by resource labels"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{
|
export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{
|
||||||
@@ -239,8 +259,16 @@ export async function listAllSiteResourcesByOrg(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
const { page, pageSize, query, mode, sort_by, order, siteId } =
|
const {
|
||||||
parsedQuery.data;
|
page,
|
||||||
|
pageSize,
|
||||||
|
query,
|
||||||
|
mode,
|
||||||
|
sort_by,
|
||||||
|
order,
|
||||||
|
siteId,
|
||||||
|
labels: labelFilter
|
||||||
|
} = parsedQuery.data;
|
||||||
|
|
||||||
const isLabelFeatureEnabled = await isLicensedOrSubscribed(
|
const isLabelFeatureEnabled = await isLicensedOrSubscribed(
|
||||||
orgId,
|
orgId,
|
||||||
@@ -276,6 +304,22 @@ export async function listAllSiteResourcesByOrg(
|
|||||||
conditions.push(eq(siteResources.mode, mode));
|
conditions.push(eq(siteResources.mode, mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isLabelFeatureEnabled && labelFilter && labelFilter.length > 0) {
|
||||||
|
conditions.push(
|
||||||
|
inArray(
|
||||||
|
siteResources.siteResourceId,
|
||||||
|
db
|
||||||
|
.select({ id: siteResourceLabels.siteResourceId })
|
||||||
|
.from(siteResourceLabels)
|
||||||
|
.innerJoin(
|
||||||
|
labels,
|
||||||
|
eq(labels.labelId, siteResourceLabels.labelId)
|
||||||
|
)
|
||||||
|
.where(inArray(labels.name, labelFilter))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
const q = "%" + query.toLowerCase() + "%";
|
const q = "%" + query.toLowerCase() + "%";
|
||||||
const queryList = [
|
const queryList = [
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
|||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import { LabelBadge } from "./label-badge";
|
import { LabelBadge } from "./label-badge";
|
||||||
import { LabelsSelector, type SelectedLabel } from "./labels-selector";
|
import { LabelsSelector, type SelectedLabel } from "./labels-selector";
|
||||||
|
import { LabelColumnFilterButton } from "./LabelColumnFilterButton";
|
||||||
|
|
||||||
export type InternalResourceSiteRow = ResourceSiteRow;
|
export type InternalResourceSiteRow = ResourceSiteRow;
|
||||||
|
|
||||||
@@ -220,59 +221,6 @@ export default function ClientResourcesTable({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function SiteCell({ resourceRow }: { resourceRow: InternalResourceRow }) {
|
|
||||||
const { siteNames, siteNiceIds, orgId } = resourceRow;
|
|
||||||
|
|
||||||
if (!siteNames || siteNames.length === 0) {
|
|
||||||
return (
|
|
||||||
<span className="text-muted-foreground">
|
|
||||||
{t("noSites", { defaultValue: "No sites" })}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteNames.length === 1) {
|
|
||||||
return (
|
|
||||||
<Link href={`/${orgId}/settings/sites/${siteNiceIds[0]}`}>
|
|
||||||
<Button variant="outline">
|
|
||||||
{siteNames[0]}
|
|
||||||
<ArrowUpRight className="ml-2 h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{siteNames.length} {t("sites")}
|
|
||||||
</span>
|
|
||||||
<ChevronDown className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="start">
|
|
||||||
{siteNames.map((siteName, idx) => (
|
|
||||||
<DropdownMenuItem key={siteNiceIds[idx]} asChild>
|
|
||||||
<Link
|
|
||||||
href={`/${orgId}/settings/sites/${siteNiceIds[idx]}`}
|
|
||||||
className="flex items-center gap-2 cursor-pointer"
|
|
||||||
>
|
|
||||||
{siteName}
|
|
||||||
<ArrowUpRight className="h-3 w-3" />
|
|
||||||
</Link>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const internalColumns = useMemo<
|
const internalColumns = useMemo<
|
||||||
ExtendedColumnDef<InternalResourceRow>[]
|
ExtendedColumnDef<InternalResourceRow>[]
|
||||||
>(() => {
|
>(() => {
|
||||||
@@ -585,9 +533,15 @@ export default function ClientResourcesTable({
|
|||||||
id: "labels",
|
id: "labels",
|
||||||
accessorKey: "labels",
|
accessorKey: "labels",
|
||||||
header: () => (
|
header: () => (
|
||||||
<span className="p-3 text-end w-full inline-block">
|
<LabelColumnFilterButton
|
||||||
{t("labels")}
|
orgId={orgId}
|
||||||
</span>
|
selectedValues={searchParams.getAll("labels")}
|
||||||
|
onSelectedValuesChange={(value) =>
|
||||||
|
handleFilterChange("labels", value)
|
||||||
|
}
|
||||||
|
label={t("labels")}
|
||||||
|
className="p-3"
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
cell: ({ row }: { row: { original: InternalResourceRow } }) => (
|
cell: ({ row }: { row: { original: InternalResourceRow } }) => (
|
||||||
<ClientResourceLabelCell
|
<ClientResourceLabelCell
|
||||||
@@ -603,13 +557,15 @@ export default function ClientResourcesTable({
|
|||||||
|
|
||||||
function handleFilterChange(
|
function handleFilterChange(
|
||||||
column: string,
|
column: string,
|
||||||
value: string | undefined | null
|
value: string | undefined | null | string[]
|
||||||
) {
|
) {
|
||||||
searchParams.delete(column);
|
searchParams.delete(column);
|
||||||
searchParams.delete("page");
|
searchParams.delete("page");
|
||||||
|
|
||||||
if (value) {
|
if (typeof value === "string") {
|
||||||
searchParams.set(column, value);
|
searchParams.set(column, value);
|
||||||
|
} else if (value) {
|
||||||
|
value.forEach((val) => searchParams.append(column, val));
|
||||||
}
|
}
|
||||||
filter({
|
filter({
|
||||||
searchParams
|
searchParams
|
||||||
|
|||||||
Reference in New Issue
Block a user