mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-20 15:55:31 +00:00
✨ labels on proxy resources
This commit is contained in:
@@ -417,7 +417,9 @@ export async function listResources(
|
||||
conditions.push(or(...queryList));
|
||||
}
|
||||
|
||||
const baseQuery = queryResourcesBase(isLabelFeatureEnabled).where(and(...conditions));
|
||||
const baseQuery = queryResourcesBase(isLabelFeatureEnabled).where(
|
||||
and(...conditions)
|
||||
);
|
||||
|
||||
// we need to add `as` so that drizzle filters the result as a subquery
|
||||
const countQuery = db.$count(baseQuery.as("filtered_resources"));
|
||||
@@ -463,7 +465,8 @@ export async function listResources(
|
||||
)
|
||||
.where(
|
||||
inArray(resourceLabels.resourceId, resourceIdList)
|
||||
);
|
||||
)
|
||||
.orderBy(asc(resourceLabels.resourceLabelId));
|
||||
}
|
||||
|
||||
const allResourceTargets =
|
||||
|
||||
@@ -423,7 +423,8 @@ export async function listSites(
|
||||
siteLabels,
|
||||
eq(siteLabels.labelId, labels.labelId)
|
||||
)
|
||||
.where(inArray(siteLabels.siteId, siteIds));
|
||||
.where(inArray(siteLabels.siteId, siteIds))
|
||||
.orderBy(asc(siteLabels.siteLabelId));
|
||||
}
|
||||
|
||||
const sitesWithUpdates: SiteWithUpdateAvailable[] = rows.map((site) => {
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
Clock,
|
||||
Funnel,
|
||||
MoreHorizontal,
|
||||
PlusIcon,
|
||||
ShieldCheck,
|
||||
ShieldOff,
|
||||
XCircle
|
||||
@@ -55,6 +56,7 @@ import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
startTransition,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useOptimistic,
|
||||
@@ -68,6 +70,8 @@ import z from "zod";
|
||||
import { ColumnFilterButton } from "./ColumnFilterButton";
|
||||
import { ControlledDataTable } from "./ui/controlled-data-table";
|
||||
import UptimeMiniBar from "./UptimeMiniBar";
|
||||
import { LabelsSelector, type SelectedLabel } from "./labels-selector";
|
||||
import { LabelBadge } from "./label-badge";
|
||||
|
||||
export type TargetHealth = {
|
||||
targetId: number;
|
||||
@@ -538,11 +542,10 @@ export default function ProxyResourcesTable({
|
||||
),
|
||||
cell: ({ row }: { row: { original: ResourceRow } }) => {
|
||||
return (
|
||||
// <SiteLabelCell
|
||||
// site={row.original}
|
||||
// orgId={orgId}
|
||||
// />
|
||||
<></>
|
||||
<ResourceLabelCell
|
||||
resource={row.original}
|
||||
orgId={orgId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -707,6 +710,105 @@ export default function ProxyResourcesTable({
|
||||
);
|
||||
}
|
||||
|
||||
type ResourceLabelCellProps = {
|
||||
resource: ResourceRow;
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
function ResourceLabelCell({ resource, orgId }: ResourceLabelCellProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const labels = resource.labels ?? [];
|
||||
const [optimisticLabels, setOptimisticLabels] = useOptimistic(labels);
|
||||
|
||||
function toggleSiteLabel(
|
||||
label: SelectedLabel,
|
||||
action: "attach" | "detach"
|
||||
) {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
if (action === "attach") {
|
||||
setOptimisticLabels([...optimisticLabels, label]);
|
||||
|
||||
await api.put(
|
||||
`/org/${orgId}/label/${label.labelId}/attach`,
|
||||
{ resourceId: resource.id }
|
||||
);
|
||||
} else {
|
||||
setOptimisticLabels(
|
||||
optimisticLabels.filter(
|
||||
(lb) => lb.labelId !== label.labelId
|
||||
)
|
||||
);
|
||||
await api.put(
|
||||
`/org/${orgId}/label/${label.labelId}/detach`,
|
||||
{ resourceId: resource.id }
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: t("error"),
|
||||
description: formatAxiosError(e, t("errorOccurred")),
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
router.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inline-flex flex-wrap items-center justify-end w-full gap-1">
|
||||
{optimisticLabels.slice(0, 3).map((label) => (
|
||||
<LabelBadge
|
||||
key={label.labelId}
|
||||
onClick={() => setIsPopoverOpen(true)}
|
||||
{...label}
|
||||
/>
|
||||
))}
|
||||
{optimisticLabels.length > 3 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"inline-flex gap-1 items-center",
|
||||
"rounded-full text-sm cursor-pointer",
|
||||
"px-1.5 py-0 h-auto"
|
||||
)}
|
||||
onClick={() => setIsPopoverOpen(true)}
|
||||
>
|
||||
+{optimisticLabels.length - 3}
|
||||
</Button>
|
||||
)}
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="p-1 size-auto rounded-full"
|
||||
title={t("addLabels")}
|
||||
>
|
||||
<span className="sr-only">{t("addLabels")}</span>
|
||||
<PlusIcon className="size-3" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="center" className="p-0 w-full">
|
||||
<LabelsSelector
|
||||
orgId={orgId}
|
||||
selectedLabels={optimisticLabels}
|
||||
toggleLabel={toggleSiteLabel}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TargetStatusCell({
|
||||
targets,
|
||||
healthStatus
|
||||
|
||||
Reference in New Issue
Block a user