Merge branch 'dev' of https://github.com/fosrl/pangolin into dev

This commit is contained in:
miloschwartz
2026-04-26 10:23:36 -07:00
31 changed files with 946 additions and 77 deletions

View File

@@ -0,0 +1,37 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table";
import { useTranslations } from "next-intl";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
onRefresh?: () => void;
isRefreshing?: boolean;
}
export function UsersDataTable<TData, TValue>({
columns,
data,
onRefresh,
isRefreshing
}: DataTableProps<TData, TValue>) {
const t = useTranslations();
return (
<DataTable
columns={columns}
data={data}
persistPageSize="userServer-table"
title={t("userServer")}
searchPlaceholder={t("userSearch")}
searchColumn="email"
onRefresh={onRefresh}
isRefreshing={isRefreshing}
enableColumnVisibility={true}
stickyLeftColumn="username"
stickyRightColumn="actions"
/>
);
}

View File

@@ -118,6 +118,8 @@ function triggerLabel(rule: AlertRuleRow, t: (k: string) => string) {
return t("alertingTriggerResourceHealthy");
case "resource_unhealthy":
return t("alertingTriggerResourceUnhealthy");
case "resource_degraded":
return t("alertingTriggerResourceDegraded");
case "resource_toggle":
return t("alertingTriggerResourceToggle");
default:

View File

@@ -47,15 +47,7 @@ import {
PopoverTrigger
} from "@app/components/ui/popover";
import { CaretSortIcon } from "@radix-ui/react-icons";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from "@app/components/ui/command";
import { CheckIcon, ChevronsUpDown } from "lucide-react";
import { ChevronsUpDown } from "lucide-react";
import { Checkbox } from "@app/components/ui/checkbox";
import { GenerateAccessTokenResponse } from "@server/routers/accessToken";
import { constructShareLink } from "@app/lib/shareLinks";
@@ -275,10 +267,11 @@ export default function CreateShareLinkForm({
</PopoverTrigger>
<PopoverContent className="p-0">
<ResourceSelector
orgId={
org.org
.orgId
}
excludeWildcard
orgId={
org.org
.orgId
}
selectedResource={
selectedResource
}

View File

@@ -32,7 +32,6 @@ 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 { useQuery } from "@tanstack/react-query";
import { AxiosResponse } from "axios";
import {
ArrowDown01Icon,

View File

@@ -0,0 +1,41 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table";
import { useTranslations } from "next-intl";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
createRole?: () => void;
onRefresh?: () => void;
isRefreshing?: boolean;
}
export function RolesDataTable<TData, TValue>({
columns,
data,
createRole,
onRefresh,
isRefreshing
}: DataTableProps<TData, TValue>) {
const t = useTranslations();
return (
<DataTable
columns={columns}
data={data}
persistPageSize="roles-table"
title={t("roles")}
searchPlaceholder={t("accessRolesSearch")}
searchColumn="name"
onAdd={createRole}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
addButtonText={t("accessRolesAdd")}
enableColumnVisibility={true}
stickyLeftColumn="name"
stickyRightColumn="actions"
/>
);
}

View File

@@ -42,7 +42,8 @@ const barColorClass: Record<string, string> = {
good: "bg-green-500",
degraded: "bg-yellow-500",
bad: "bg-red-500",
no_data: "bg-neutral-200 dark:bg-neutral-700"
no_data: "bg-neutral-200 dark:bg-neutral-700",
unknown: "bg-neutral-200 dark:bg-neutral-700"
};
type UptimeBarProps = {
@@ -188,7 +189,7 @@ export default function UptimeBar({
<div className="font-semibold text-xs">
{formatDate(day.date)}
</div>
{day.status !== "no_data" && (
{day.status !== "no_data" && day.status !== "unknown" && (
<div className="text-xs text-primary-foreground/80">
{t("uptimeTooltipUptimeLabel")}:{" "}
<span className="font-medium text-primary-foreground">
@@ -224,7 +225,7 @@ export default function UptimeBar({
))}
</div>
)}
{day.status === "no_data" && (
{(day.status === "no_data" || day.status === "unknown") && (
<div className="text-xs text-primary-foreground/60">
{t("uptimeNoMonitoringData")}
</div>

View File

@@ -34,7 +34,8 @@ const barColorClass: Record<string, string> = {
good: "bg-green-500",
degraded: "bg-yellow-500",
bad: "bg-red-500",
no_data: "bg-neutral-200 dark:bg-neutral-700"
no_data: "bg-neutral-200 dark:bg-neutral-700",
unknown: "bg-neutral-200 dark:bg-neutral-700"
};
type UptimeMiniBarProps = {
@@ -137,7 +138,7 @@ export default function UptimeMiniBar({
{formatDate(day.date)}
</div>
<div className="text-xs text-primary-foreground/80">
{day.status === "no_data"
{day.status === "no_data" || day.status === "unknown"
? t("uptimeNoData")
: `${day.uptimePercent.toFixed(1)}% ${t("uptimeSuffix")}`}
</div>

View File

@@ -0,0 +1,41 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table";
import { useTranslations } from "next-intl";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
inviteUser?: () => void;
onRefresh?: () => void;
isRefreshing?: boolean;
}
export function UsersDataTable<TData, TValue>({
columns,
data,
inviteUser,
onRefresh,
isRefreshing
}: DataTableProps<TData, TValue>) {
const t = useTranslations();
return (
<DataTable
columns={columns}
data={data}
persistPageSize="users-table"
title={t("users")}
searchPlaceholder={t("accessUsersSearch")}
searchColumn="email"
onAdd={inviteUser}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
addButtonText={t("accessUserCreate")}
enableColumnVisibility={true}
stickyLeftColumn="displayUsername"
stickyRightColumn="actions"
/>
);
}

View File

@@ -1147,6 +1147,7 @@ export function AlertRuleSourceFields({
if (
curTrigger !== "resource_healthy" &&
curTrigger !== "resource_unhealthy" &&
curTrigger !== "resource_degraded" &&
curTrigger !== "resource_toggle"
) {
setValue("trigger", "resource_toggle", {
@@ -1367,6 +1368,9 @@ export function AlertRuleTriggerFields({
<SelectItem value="resource_unhealthy">
{t("alertingTriggerResourceUnhealthy")}
</SelectItem>
<SelectItem value="resource_degraded">
{t("alertingTriggerResourceDegraded")}
</SelectItem>
</>
) : (
<>

View File

@@ -17,19 +17,21 @@ import { useDebounce } from "use-debounce";
export type SelectedResource = Pick<
ListResourcesResponse["resources"][number],
"name" | "resourceId" | "fullDomain" | "niceId" | "ssl"
"name" | "resourceId" | "fullDomain" | "niceId" | "ssl" | "wildcard"
>;
export type ResourceSelectorProps = {
orgId: string;
selectedResource?: SelectedResource | null;
onSelectResource: (resource: SelectedResource) => void;
excludeWildcard?: boolean;
};
export function ResourceSelector({
orgId,
selectedResource,
onSelectResource
onSelectResource,
excludeWildcard = false
}: ResourceSelectorProps) {
const t = useTranslations();
const [resourceSearchQuery, setResourceSearchQuery] = useState("");
@@ -46,10 +48,13 @@ export function ResourceSelector({
// always include the selected resource in the list of resources shown
const resourcesShown = useMemo(() => {
const allResources: Array<SelectedResource> = [...resources];
const allResources: Array<SelectedResource> = excludeWildcard
? resources.filter((r) => !r.wildcard)
: [...resources];
if (
debouncedSearchQuery.trim().length === 0 &&
selectedResource &&
!(excludeWildcard && selectedResource.wildcard) &&
!allResources.find(
(resource) =>
resource.resourceId === selectedResource?.resourceId
@@ -58,7 +63,7 @@ export function ResourceSelector({
allResources.unshift(selectedResource);
}
return allResources;
}, [debouncedSearchQuery, resources, selectedResource]);
}, [debouncedSearchQuery, resources, selectedResource, excludeWildcard]);
return (
<Command shouldFilter={false}>