fix(webapp): restore admin debug tooltip on Tasks and Runs, make its IDs copyable (#4332)
Restores the debug panel on the **Tasks** and **Runs** pages, and makes the data it shows copyable. Admin/impersonation only — no change for regular users, so there's no `.server-changes` <img width="909" height="1420" alt="CleanShot 2026-07-22 at 12 05 27@2x" src="https://github.com/user-attachments/assets/ce2da167-dc23-422f-83f3-4f4aee9ed32c" />
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
|
||||
import { CopyableText } from "~/components/primitives/CopyableText";
|
||||
import * as Property from "~/components/primitives/PropertyTable";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -25,7 +26,10 @@ export function AdminDebugTooltip({ children }: { children?: React.ReactNode })
|
||||
<TooltipTrigger>
|
||||
<ShieldCheckIcon className="size-5" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-h-[90vh] overflow-y-auto">
|
||||
{/* The copy controls below pass `hideTooltip` so their own tooltips don't fire
|
||||
Radix's global close and dismiss this panel. `pr-8` leaves room for the
|
||||
copy button, which is absolutely positioned to the right of each value. */}
|
||||
<TooltipContent className="max-h-[90vh] overflow-y-auto pr-8">
|
||||
<Content>{children}</Content>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -44,23 +48,31 @@ function Content({ children }: { children: React.ReactNode }) {
|
||||
<Property.Table>
|
||||
<Property.Item>
|
||||
<Property.Label>User ID</Property.Label>
|
||||
<Property.Value>{user.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={user.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
{organization && (
|
||||
<Property.Item>
|
||||
<Property.Label>Org ID</Property.Label>
|
||||
<Property.Value>{organization.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={organization.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
)}
|
||||
{project && (
|
||||
<>
|
||||
<Property.Item>
|
||||
<Property.Label>Project ID</Property.Label>
|
||||
<Property.Value>{project.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={project.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Project ref</Property.Label>
|
||||
<Property.Value>{project.externalRef}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={project.externalRef} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
</>
|
||||
)}
|
||||
@@ -68,7 +80,9 @@ function Content({ children }: { children: React.ReactNode }) {
|
||||
<>
|
||||
<Property.Item>
|
||||
<Property.Label>Environment ID</Property.Label>
|
||||
<Property.Value>{environment.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={environment.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Environment type</Property.Label>
|
||||
@@ -81,7 +95,7 @@ function Content({ children }: { children: React.ReactNode }) {
|
||||
</>
|
||||
)}
|
||||
</Property.Table>
|
||||
<div className="pt-2">{children}</div>
|
||||
{children && <div className="pt-2">{children}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,12 +11,19 @@ export function CopyableText({
|
||||
className,
|
||||
asChild,
|
||||
variant,
|
||||
hideTooltip,
|
||||
}: {
|
||||
value: string;
|
||||
copyValue?: string;
|
||||
className?: string;
|
||||
asChild?: boolean;
|
||||
variant?: "icon-right" | "text-below";
|
||||
/**
|
||||
* Hide the "Copy"/"Copied" hint tooltip. Use when this is rendered inside another
|
||||
* Radix tooltip (e.g. the admin debug panel): the nested tooltip would otherwise
|
||||
* fire Radix's global "one tooltip open at a time" close and dismiss the parent.
|
||||
*/
|
||||
hideTooltip?: boolean;
|
||||
}) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const { copy, copied } = useCopy(copyValue ?? value);
|
||||
@@ -24,6 +31,24 @@ export function CopyableText({
|
||||
const resolvedVariant = variant ?? "icon-right";
|
||||
|
||||
if (resolvedVariant === "icon-right") {
|
||||
const iconButton = (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-1 flex size-6 items-center justify-center rounded border border-border-bright bg-background-hover",
|
||||
asChild && "p-1",
|
||||
copied
|
||||
? "text-green-500"
|
||||
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
|
||||
)}
|
||||
>
|
||||
{copied ? (
|
||||
<ClipboardCheckIcon className="size-3.5" />
|
||||
) : (
|
||||
<ClipboardIcon className="size-3.5" />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cn("group relative inline-flex h-6 items-center", className)}
|
||||
@@ -38,29 +63,17 @@ export function CopyableText({
|
||||
isHovered ? "flex" : "hidden"
|
||||
)}
|
||||
>
|
||||
<SimpleTooltip
|
||||
button={
|
||||
<span
|
||||
className={cn(
|
||||
"ml-1 flex size-6 items-center justify-center rounded border border-border-bright bg-background-hover",
|
||||
asChild && "p-1",
|
||||
copied
|
||||
? "text-green-500"
|
||||
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
|
||||
)}
|
||||
>
|
||||
{copied ? (
|
||||
<ClipboardCheckIcon className="size-3.5" />
|
||||
) : (
|
||||
<ClipboardIcon className="size-3.5" />
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
content={copied ? "Copied!" : "Copy"}
|
||||
className="font-sans"
|
||||
disableHoverableContent
|
||||
asChild={asChild}
|
||||
/>
|
||||
{hideTooltip ? (
|
||||
iconButton
|
||||
) : (
|
||||
<SimpleTooltip
|
||||
button={iconButton}
|
||||
content={copied ? "Copied!" : "Copy"}
|
||||
className="font-sans"
|
||||
disableHoverableContent
|
||||
asChild={asChild}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
+2
@@ -16,6 +16,7 @@ import { PlusIcon } from "~/assets/icons/PlusIcon";
|
||||
import { QuestionMarkIcon } from "~/assets/icons/QuestionMarkIcon";
|
||||
import { RunsIcon } from "~/assets/icons/RunsIcon";
|
||||
import { TaskIcon } from "~/assets/icons/TaskIcon";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
import { CodeBlock } from "~/components/code/CodeBlock";
|
||||
import { InlineCode } from "~/components/code/InlineCode";
|
||||
import { HasNoTasksDeployed, HasNoTasksDev } from "~/components/BlankStatePanels";
|
||||
@@ -261,6 +262,7 @@ export default function Page() {
|
||||
<NavBar>
|
||||
<PageTitle title="Tasks" accessory={<TasksHelpTooltip />} />
|
||||
<PageAccessories>
|
||||
<AdminDebugTooltip />
|
||||
<LinkButton
|
||||
variant={"docs/small"}
|
||||
LeadingIcon={BookOpenIcon}
|
||||
|
||||
+4
-1
@@ -2,6 +2,7 @@ import { BookOpenIcon } from "@heroicons/react/20/solid";
|
||||
import { type MetaFunction } from "@remix-run/react";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
import { CopyableText } from "~/components/primitives/CopyableText";
|
||||
import { CodeBlock } from "~/components/code/CodeBlock";
|
||||
import { InlineCode } from "~/components/code/InlineCode";
|
||||
import {
|
||||
@@ -113,7 +114,9 @@ export default function Page() {
|
||||
<Property.Table>
|
||||
<Property.Item key={environment.id}>
|
||||
<Property.Label>{environment.slug}</Property.Label>
|
||||
<Property.Value>{environment.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={environment.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
</Property.Table>
|
||||
</AdminDebugTooltip>
|
||||
|
||||
+3
-1
@@ -242,7 +242,9 @@ export default function Page() {
|
||||
{branches.map((branch) => (
|
||||
<Property.Item key={branch.id}>
|
||||
<Property.Label>{branch.branchName}</Property.Label>
|
||||
<Property.Value>{branch.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={branch.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
))}
|
||||
</Property.Table>
|
||||
|
||||
+4
-1
@@ -21,6 +21,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import simplur from "simplur";
|
||||
import { z } from "zod";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
import { CopyableText } from "~/components/primitives/CopyableText";
|
||||
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
|
||||
import { Feedback } from "~/components/Feedback";
|
||||
import {
|
||||
@@ -270,7 +271,9 @@ export default function Page() {
|
||||
{environment.type}{" "}
|
||||
{environment.branchName ? ` (${environment.branchName})` : ""}
|
||||
</Property.Label>
|
||||
<Property.Value>{environment.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={environment.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
))}
|
||||
</Property.Table>
|
||||
|
||||
+13
-4
@@ -18,6 +18,7 @@ import { GitMetadata } from "~/components/GitMetadata";
|
||||
import { VercelLink } from "~/components/integrations/VercelLink";
|
||||
import { RuntimeIcon } from "~/components/RuntimeIcon";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
import { CopyableText } from "~/components/primitives/CopyableText";
|
||||
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
|
||||
import { Badge } from "~/components/primitives/Badge";
|
||||
import { LinkButton } from "~/components/primitives/Buttons";
|
||||
@@ -283,20 +284,28 @@ export default function Page() {
|
||||
<Property.Table>
|
||||
<Property.Item>
|
||||
<Property.Label>ID</Property.Label>
|
||||
<Property.Value>{deployment.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={deployment.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Project ID</Property.Label>
|
||||
<Property.Value>{deployment.projectId}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={deployment.projectId} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Org ID</Property.Label>
|
||||
<Property.Value>{deployment.organizationId}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={deployment.organizationId} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
{deployment.imageReference && (
|
||||
<Property.Item>
|
||||
<Property.Label>Image</Property.Label>
|
||||
<Property.Value>{deployment.imageReference}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={deployment.imageReference} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
)}
|
||||
<Property.Item>
|
||||
|
||||
+3
-1
@@ -95,7 +95,9 @@ export default function Page() {
|
||||
{branches.map((branch) => (
|
||||
<Property.Item key={branch.id}>
|
||||
<Property.Label>{branch.branchName}</Property.Label>
|
||||
<Property.Value>{branch.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={branch.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
))}
|
||||
</Property.Table>
|
||||
|
||||
+4
-1
@@ -7,6 +7,7 @@ import { Gauge } from "lucide-react";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { ConcurrencyIcon } from "~/assets/icons/ConcurrencyIcon";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
import { CopyableText } from "~/components/primitives/CopyableText";
|
||||
import { Feedback } from "~/components/Feedback";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { EnvironmentSelector } from "~/components/navigation/EnvironmentSelector";
|
||||
@@ -125,7 +126,9 @@ export default function Page() {
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Organization ID</Property.Label>
|
||||
<Property.Value>{data.organizationId}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={data.organizationId} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
</Property.Table>
|
||||
</AdminDebugTooltip>
|
||||
|
||||
+3
-1
@@ -163,7 +163,9 @@ export default function Page() {
|
||||
{regions.map((region) => (
|
||||
<Property.Item key={region.id}>
|
||||
<Property.Label>{region.name}</Property.Label>
|
||||
<Property.Value>{region.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={region.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
))}
|
||||
</Property.Table>
|
||||
|
||||
+12
-4
@@ -489,19 +489,27 @@ export default function Page() {
|
||||
<Property.Table>
|
||||
<Property.Item>
|
||||
<Property.Label>ID</Property.Label>
|
||||
<Property.Value>{run.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={run.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Trace ID</Property.Label>
|
||||
<Property.Value>{run.traceId}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={run.traceId} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Env ID</Property.Label>
|
||||
<Property.Value>{run.environment.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={run.environment.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Org ID</Property.Label>
|
||||
<Property.Value>{run.environment.organizationId}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={run.environment.organizationId} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
</Property.Table>
|
||||
</AdminDebugTooltip>
|
||||
|
||||
+2
@@ -11,6 +11,7 @@ import {
|
||||
import { ListCheckedIcon } from "~/assets/icons/ListCheckedIcon";
|
||||
import { QuestionMarkIcon } from "~/assets/icons/QuestionMarkIcon";
|
||||
import { TaskIcon } from "~/assets/icons/TaskIcon";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
import { DevDisconnectedBanner, useDevPresence } from "~/components/DevPresence";
|
||||
import { InlineCode } from "~/components/code/InlineCode";
|
||||
import { StepContentContainer } from "~/components/StepContentContainer";
|
||||
@@ -166,6 +167,7 @@ export default function Page() {
|
||||
<DevDisconnectedBanner isConnected={isConnected} />
|
||||
)}
|
||||
<PageAccessories>
|
||||
<AdminDebugTooltip />
|
||||
<LinkButton
|
||||
variant={"docs/small"}
|
||||
LeadingIcon={BookOpenIcon}
|
||||
|
||||
+7
-6
@@ -2,9 +2,9 @@ import { Outlet, type MetaFunction } from "@remix-run/react";
|
||||
import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import * as Property from "~/components/primitives/PropertyTable";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
import { CopyableText } from "~/components/primitives/CopyableText";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import {
|
||||
@@ -55,14 +55,15 @@ export default function SettingsLayout() {
|
||||
<Property.Table>
|
||||
<Property.Item>
|
||||
<Property.Label>ID</Property.Label>
|
||||
<Property.Value>{project.id}</Property.Value>
|
||||
<div className="flex items-center gap-2">
|
||||
<Paragraph variant="extra-small/bright/mono">{project.id}</Paragraph>
|
||||
</div>
|
||||
<Property.Value>
|
||||
<CopyableText value={project.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
<Property.Item>
|
||||
<Property.Label>Org ID</Property.Label>
|
||||
<Property.Value>{project.organizationId}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={project.organizationId} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
</Property.Table>
|
||||
</AdminDebugTooltip>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { z } from "zod";
|
||||
import { Feedback } from "~/components/Feedback";
|
||||
import { UserAvatar } from "~/components/UserProfilePhoto";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
import { CopyableText } from "~/components/primitives/CopyableText";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import {
|
||||
Alert,
|
||||
@@ -372,7 +373,9 @@ export default function Page() {
|
||||
<Property.Table>
|
||||
<Property.Item>
|
||||
<Property.Label>Org ID</Property.Label>
|
||||
<Property.Value>{organization.id}</Property.Value>
|
||||
<Property.Value>
|
||||
<CopyableText value={organization.id} asChild hideTooltip />
|
||||
</Property.Value>
|
||||
</Property.Item>
|
||||
|
||||
{members.map((member) => (
|
||||
|
||||
Reference in New Issue
Block a user