diff --git a/apps/dashboard/src/components/chat/tool-renderers/list-private-locations.tsx b/apps/dashboard/src/components/chat/tool-renderers/list-private-locations.tsx
index 0e4f6277..3471bbf6 100644
--- a/apps/dashboard/src/components/chat/tool-renderers/list-private-locations.tsx
+++ b/apps/dashboard/src/components/chat/tool-renderers/list-private-locations.tsx
@@ -2,6 +2,7 @@ import type { AgentToolOutput } from "@openstatus/services/agent-tools";
import { TableCellDate } from "@/components/data-table/table-cell-date";
import { TableCellLink } from "@/components/data-table/table-cell-link";
+import { TableCellMetadata } from "@/components/data-table/table-cell-metadata";
import { TableCellNumber } from "@/components/data-table/table-cell-number";
import { TableCellText } from "@/components/data-table/table-cell-text";
@@ -11,13 +12,14 @@ type Output = AgentToolOutput<"list_private_locations">;
export function listPrivateLocationsTable(
output: Output,
-): ResultTableData<"name" | "monitors" | "lastSeenAt" | "id"> {
+): ResultTableData<"name" | "monitors" | "metadata" | "lastSeenAt" | "id"> {
const items = output?.items ?? [];
return {
empty: "No private locations.",
columns: [
{ key: "name", header: "Name" },
{ key: "monitors", header: "Monitors" },
+ { key: "metadata", header: "Metadata" },
{ key: "lastSeenAt", header: "Last seen" },
{ key: "id", header: "ID" },
],
@@ -40,6 +42,7 @@ export function listPrivateLocationsTable(
className="text-muted-foreground font-mono"
/>
),
+ metadata: ,
lastSeenAt: location.lastSeenAt ? (
) : (
diff --git a/apps/dashboard/src/components/controls-search/command-region.tsx b/apps/dashboard/src/components/controls-search/command-region.tsx
index db8f7dbe..5b0805c8 100644
--- a/apps/dashboard/src/components/controls-search/command-region.tsx
+++ b/apps/dashboard/src/components/controls-search/command-region.tsx
@@ -28,6 +28,10 @@ import {
BillingOverlayButton,
BillingOverlayDescription,
} from "@/components/content/billing-overlay";
+import {
+ TableCellMetadata,
+ getMetadataEntries,
+} from "@/components/data-table/table-cell-metadata";
import type { REGIONS } from "@/data/metrics.client";
import { useTRPC } from "@/lib/trpc/client";
@@ -36,7 +40,11 @@ export function CommandRegion({
privateLocations,
}: {
regions: (typeof REGIONS)[number][];
- privateLocations?: { id: number; name: string }[];
+ privateLocations?: {
+ id: number;
+ name: string;
+ metadata?: Record | null;
+ }[];
}) {
const trpc = useTRPC();
const { data: workspace } = useQuery(trpc.workspace.get.queryOptions());
@@ -151,31 +159,44 @@ export function CommandRegion({
)}
{privateLocations && privateLocations.length > 0 ? (
- {privateLocations.map((location) => (
- {
- setSelectedRegions((prev) =>
- prev.includes(location.id.toString())
- ? prev.filter((r) => r !== location.id.toString())
- : [...prev, location.id.toString()],
- );
- }}
- >
-
- {location.name}
-
-
- ))}
+ {privateLocations.map((location) => {
+ const metadata = getMetadataEntries(location.metadata);
+ const keywords = [location.name, ...metadata.flat()];
+
+ return (
+ {
+ setSelectedRegions((prev) =>
+ prev.includes(location.id.toString())
+ ? prev.filter((r) => r !== location.id.toString())
+ : [...prev, location.id.toString()],
+ );
+ }}
+ >
+
+
+ {location.name}
+
+ {metadata.length > 0 ? (
+
+ ) : null}
+
+
+ );
+ })}
) : null}
No region found.
diff --git a/apps/dashboard/src/components/data-table/private-locations/columns.tsx b/apps/dashboard/src/components/data-table/private-locations/columns.tsx
index d01f8cc2..c4c2a6cd 100644
--- a/apps/dashboard/src/components/data-table/private-locations/columns.tsx
+++ b/apps/dashboard/src/components/data-table/private-locations/columns.tsx
@@ -9,6 +9,7 @@ import { cn } from "@/lib/utils";
import { TableCellBadge } from "../table-cell-badge";
import { TableCellDate } from "../table-cell-date";
+import { TableCellMetadata } from "../table-cell-metadata";
import { TableCellText } from "../table-cell-text";
import { DataTableRowActions } from "./data-table-row-actions";
@@ -43,6 +44,15 @@ export const columns: ColumnDef[] = [
);
},
},
+ {
+ accessorKey: "metadata",
+ header: ({ column }) => (
+
+ ),
+ enableSorting: false,
+ enableHiding: true,
+ cell: ({ row }) => ,
+ },
{
accessorKey: "lastSeenAt",
header: ({ column }) => (
diff --git a/apps/dashboard/src/components/data-table/table-cell-metadata.tsx b/apps/dashboard/src/components/data-table/table-cell-metadata.tsx
new file mode 100644
index 00000000..40a6e334
--- /dev/null
+++ b/apps/dashboard/src/components/data-table/table-cell-metadata.tsx
@@ -0,0 +1,109 @@
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@openstatus/ui/components/ui/tooltip";
+import { useEffect, useRef, useState } from "react";
+
+import { Pill } from "@/components/common/pill";
+import { cn } from "@/lib/utils";
+
+/** Sorted `[key, value]` pairs with empty values dropped; `[]` for non-objects. */
+export function getMetadataEntries(value: unknown): [string, string][] {
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
+ return Object.entries(value)
+ .filter((entry): entry is [string, string] => {
+ return typeof entry[1] === "string" && entry[1].trim().length > 0;
+ })
+ .sort(([a], [b]) => a.localeCompare(b));
+}
+
+export function TableCellMetadata({
+ value,
+ maxEntries,
+ className,
+ ...props
+}: React.ComponentProps<"div"> & {
+ value: unknown;
+ /** Collapse the rest into a `+n` chip with the full list in a tooltip. */
+ maxEntries?: number;
+}) {
+ const entries = getMetadataEntries(value);
+
+ if (entries.length === 0) {
+ return (
+
+ -
+
+ );
+ }
+
+ const visible = maxEntries ? entries.slice(0, maxEntries) : entries;
+ const hidden = maxEntries ? entries.slice(maxEntries) : [];
+
+ return (
+
+
+ {visible.map(([key, val]) => (
+
+ ))}
+ {hidden.length > 0 ? (
+
+
+
+ +{hidden.length}
+
+
+
+ {hidden.map(([key, val]) => (
+
+ {key}: {val}
+
+ ))}
+
+
+ ) : null}
+
+
+ );
+}
+
+function MetadataPill({ label, value }: { label: string; value: string }) {
+ const ref = useRef(null);
+ const [isTruncated, setIsTruncated] = useState(false);
+
+ useEffect(() => {
+ if (ref.current) {
+ setIsTruncated(ref.current.scrollWidth > ref.current.clientWidth);
+ }
+ }, [value]);
+
+ const pill = (
+
+ {value}
+
+ }
+ variant="outline"
+ />
+ );
+
+ if (!isTruncated) return pill;
+
+ return (
+
+ event.preventDefault()} asChild>
+ {pill}
+
+
+ {label}: {value}
+
+
+ );
+}
diff --git a/apps/dashboard/src/components/forms/monitor/form-scheduling-regions.tsx b/apps/dashboard/src/components/forms/monitor/form-scheduling-regions.tsx
index 884e1cff..df06bf1b 100644
--- a/apps/dashboard/src/components/forms/monitor/form-scheduling-regions.tsx
+++ b/apps/dashboard/src/components/forms/monitor/form-scheduling-regions.tsx
@@ -33,6 +33,10 @@ import { z } from "zod";
import { IconCloudProviderTooltip } from "@/components/common/icon-cloud-provider";
import { Link } from "@/components/common/link";
import { Note, NoteButton } from "@/components/common/note";
+import {
+ TableCellMetadata,
+ getMetadataEntries,
+} from "@/components/data-table/table-cell-metadata";
import { UpgradeDialog } from "@/components/dialogs/upgrade";
import {
FormCard,
@@ -49,7 +53,11 @@ import { useTRPC } from "@/lib/trpc/client";
const DEFAULT_PERIODICITY = "10m";
const DEFAULT_REGIONS = ["ams", "fra", "iad", "syd", "jnb", "gru"];
const PERIODICITY = monitorPeriodicity.filter((p) => p !== "other");
-const DEFAULT_PRIVATE_LOCATIONS = [] satisfies { id: number; name: string }[];
+const DEFAULT_PRIVATE_LOCATIONS = [] satisfies {
+ id: number;
+ name: string;
+ metadata?: Record | null;
+}[];
const schema = z.object({
regions: z.array(z.string()),
@@ -67,7 +75,11 @@ export function FormSchedulingRegions({
}: Omit, "onSubmit"> & {
defaultValues?: FormValues;
onSubmit: (values: FormValues) => Promise;
- privateLocations: { id: number; name: string }[];
+ privateLocations: {
+ id: number;
+ name: string;
+ metadata?: Record | null;
+ }[];
}) {
const trpc = useTRPC();
const [openDialog, setOpenDialog] = useState(false);
@@ -402,6 +414,7 @@ export function FormSchedulingRegions({
>
-
- {item.name}
+
+
+ {item.name}
+
+ {getMetadataEntries(item.metadata).length >
+ 0 ? (
+
+ ) : null}