diff --git a/site/src/modules/apps/WorkspaceAppFrame.stories.tsx b/site/src/modules/apps/WorkspaceAppFrame.stories.tsx new file mode 100644 index 0000000000..f7e56fad84 --- /dev/null +++ b/site/src/modules/apps/WorkspaceAppFrame.stories.tsx @@ -0,0 +1,45 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { + MockUserOwner, + MockWorkspace, + MockWorkspaceAgent, + MockWorkspaceApp, +} from "#/testHelpers/entities"; +import { withAuthProvider, withProxyProvider } from "#/testHelpers/storybook"; +import { WorkspaceAppFrame } from "./WorkspaceAppFrame"; +import type { WorkspaceAppWithAgent } from "./workspaceApps"; + +const meta: Meta = { + title: "modules/apps/WorkspaceAppFrame", + component: WorkspaceAppFrame, + decorators: [withAuthProvider, withProxyProvider()], + parameters: { + layout: "fullscreen", + user: MockUserOwner, + }, + args: { + workspace: MockWorkspace, + app: buildWorkspaceApp(), + active: true, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Unhealthy: Story = { + args: { + app: buildWorkspaceApp({ health: "unhealthy" }), + }, +}; + +function buildWorkspaceApp( + overrides: Partial = {}, +): WorkspaceAppWithAgent { + return { + ...MockWorkspaceApp, + agent: MockWorkspaceAgent, + health: "healthy", + ...overrides, + }; +} diff --git a/site/src/pages/TaskPage/TaskAppIframe.tsx b/site/src/modules/apps/WorkspaceAppFrame.tsx similarity index 77% rename from site/src/pages/TaskPage/TaskAppIframe.tsx rename to site/src/modules/apps/WorkspaceAppFrame.tsx index cfbca72746..4ff610f88a 100644 --- a/site/src/pages/TaskPage/TaskAppIframe.tsx +++ b/site/src/modules/apps/WorkspaceAppFrame.tsx @@ -3,7 +3,7 @@ import { ExternalLinkIcon, HouseIcon, } from "lucide-react"; -import { type FC, type HTMLProps, useRef } from "react"; +import { type ComponentProps, type FC, useRef } from "react"; import { Link as RouterLink } from "react-router"; import type { Workspace } from "#/api/typesGenerated"; import { Button } from "#/components/Button/Button"; @@ -15,18 +15,20 @@ import { } from "#/components/DropdownMenu/DropdownMenu"; import { Spinner } from "#/components/Spinner/Spinner"; import { useProxy } from "#/contexts/ProxyContext"; -import { useAppLink } from "#/modules/apps/useAppLink"; -import type { WorkspaceAppWithAgent } from "#/modules/tasks/apps"; import { cn } from "#/utils/cn"; -import { TaskWildcardWarning } from "./TaskWildcardWarning"; +import { isAppBlockedByMissingWildcard } from "./apps"; +import { useAppLink } from "./useAppLink"; +import { WorkspaceWildcardWarning } from "./WorkspaceWildcardWarning"; +import type { WorkspaceAppWithAgent } from "./workspaceApps"; -type TaskAppIFrameProps = { +type WorkspaceAppFrameProps = { workspace: Workspace; app: WorkspaceAppWithAgent; + // Keep the iframe mounted while hidden so callers can preserve app state. active: boolean; }; -export const TaskAppIFrame: FC = ({ +export const WorkspaceAppFrame: FC = ({ workspace, app, active, @@ -37,20 +39,24 @@ export const TaskAppIFrame: FC = ({ }); const proxy = useProxy(); const frameRef = useRef(null); - const shouldDisplayWildcardWarning = - app.subdomain && !proxy.proxy?.preferredWildcardHostname; + const shouldDisplayWildcardWarning = isAppBlockedByMissingWildcard( + app, + proxy.proxy?.preferredWildcardHostname, + ); + // The "preview" app renders a navigation toolbar above its iframe. + const showToolbar = app.slug === "preview"; if (shouldDisplayWildcardWarning) { return (
- +
); } return (
- {app.slug === "preview" && ( + {showToolbar && (
- {/* Possibly we will put a URL bar here, but for now we cannot due to - * cross-origin restrictions in iframes. */} -
+
@@ -79,7 +83,7 @@ export const TaskAppIFrame: FC = ({ - + Open app in new tab @@ -90,7 +94,7 @@ export const TaskAppIFrame: FC = ({ )} {app.health === "healthy" || app.health === "disabled" ? ( - + ) : app.health === "unhealthy" ? (

@@ -143,11 +147,16 @@ export const TaskAppIFrame: FC = ({ ); }; -type TaskIframeProps = HTMLProps; +type WorkspaceIframeProps = ComponentProps<"iframe">; -export const TaskIframe: FC = ({ className, ...props }) => { +export const WorkspaceIframe: FC = ({ + className, + ref, + ...props +}) => { return (