mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-21 08:15:17 +00:00
✨ labels on proxy resources
This commit is contained in:
@@ -417,7 +417,9 @@ export async function listResources(
|
|||||||
conditions.push(or(...queryList));
|
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
|
// we need to add `as` so that drizzle filters the result as a subquery
|
||||||
const countQuery = db.$count(baseQuery.as("filtered_resources"));
|
const countQuery = db.$count(baseQuery.as("filtered_resources"));
|
||||||
@@ -463,7 +465,8 @@ export async function listResources(
|
|||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
inArray(resourceLabels.resourceId, resourceIdList)
|
inArray(resourceLabels.resourceId, resourceIdList)
|
||||||
);
|
)
|
||||||
|
.orderBy(asc(resourceLabels.resourceLabelId));
|
||||||
}
|
}
|
||||||
|
|
||||||
const allResourceTargets =
|
const allResourceTargets =
|
||||||
|
|||||||
@@ -423,7 +423,8 @@ export async function listSites(
|
|||||||
siteLabels,
|
siteLabels,
|
||||||
eq(siteLabels.labelId, labels.labelId)
|
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) => {
|
const sitesWithUpdates: SiteWithUpdateAvailable[] = rows.map((site) => {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
Funnel,
|
Funnel,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
|
PlusIcon,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
ShieldOff,
|
ShieldOff,
|
||||||
XCircle
|
XCircle
|
||||||
@@ -55,6 +56,7 @@ import { useTranslations } from "next-intl";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
|
startTransition,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useOptimistic,
|
useOptimistic,
|
||||||
@@ -68,6 +70,8 @@ import z from "zod";
|
|||||||
import { ColumnFilterButton } from "./ColumnFilterButton";
|
import { ColumnFilterButton } from "./ColumnFilterButton";
|
||||||
import { ControlledDataTable } from "./ui/controlled-data-table";
|
import { ControlledDataTable } from "./ui/controlled-data-table";
|
||||||
import UptimeMiniBar from "./UptimeMiniBar";
|
import UptimeMiniBar from "./UptimeMiniBar";
|
||||||
|
import { LabelsSelector, type SelectedLabel } from "./labels-selector";
|
||||||
|
import { LabelBadge } from "./label-badge";
|
||||||
|
|
||||||
export type TargetHealth = {
|
export type TargetHealth = {
|
||||||
targetId: number;
|
targetId: number;
|
||||||
@@ -538,11 +542,10 @@ export default function ProxyResourcesTable({
|
|||||||
),
|
),
|
||||||
cell: ({ row }: { row: { original: ResourceRow } }) => {
|
cell: ({ row }: { row: { original: ResourceRow } }) => {
|
||||||
return (
|
return (
|
||||||
// <SiteLabelCell
|
<ResourceLabelCell
|
||||||
// site={row.original}
|
resource={row.original}
|
||||||
// orgId={orgId}
|
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({
|
function TargetStatusCell({
|
||||||
targets,
|
targets,
|
||||||
healthStatus
|
healthStatus
|
||||||
|
|||||||
Reference in New Issue
Block a user