mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: make Coder Agents right sidebar app and port tabs generally available (#26906)
The workspace-app and port preview tabs in the Coder Agents right panel were gated behind the `agent-app-tabs` deployment experiment. This removes the experiment entirely and renders the app and port tabs unconditionally, so the add-panel dropdown, workspace-app tabs, and port preview tabs are always available alongside terminals. ## Changes - Remove the `ExperimentAgentAppTabs` constant, its `DisplayName()` case, and its `ExperimentsKnown` registration in `codersdk/deployment.go`, then regenerate `site/src/api/typesGenerated.ts`, `coderd/apidoc/docs.go`, `coderd/apidoc/swagger.json`, and `docs/reference/api/schemas.md`. - Drop the frontend experiment gate in `AgentChatPageView.tsx` (including the now-unused `useDashboard`/`experiments` usage) so persisted app and port tabs are no longer filtered out. - Remove the `appExperimentEnabled` prop from `RightPanelAddTabControl` and render the add-panel dropdown unconditionally; update the stories accordingly. This reverses the gating introduced in #26395. note: the diff is tiny if you hide whitespace changes
This commit is contained in:
Generated
-4
@@ -19630,13 +19630,11 @@ const docTemplate = `{
|
|||||||
"nats_pubsub",
|
"nats_pubsub",
|
||||||
"minimum-implicit-member",
|
"minimum-implicit-member",
|
||||||
"ai-gateway-cost-control",
|
"ai-gateway-cost-control",
|
||||||
"agent-app-tabs",
|
|
||||||
"chat-advisor",
|
"chat-advisor",
|
||||||
"chat-virtual-desktop"
|
"chat-virtual-desktop"
|
||||||
],
|
],
|
||||||
"x-enum-comments": {
|
"x-enum-comments": {
|
||||||
"ExperimentAIGatewayCostControl": "Enables AI Gateway cost control functionality.",
|
"ExperimentAIGatewayCostControl": "Enables AI Gateway cost control functionality.",
|
||||||
"ExperimentAgentAppTabs": "Enables workspace-app and port preview tabs in the Coder Agents right panel.",
|
|
||||||
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
||||||
"ExperimentChatAdvisor": "Enables the advisor tool for root agent chats.",
|
"ExperimentChatAdvisor": "Enables the advisor tool for root agent chats.",
|
||||||
"ExperimentChatVirtualDesktop": "Enables virtual desktop and computer use provider for agents.",
|
"ExperimentChatVirtualDesktop": "Enables virtual desktop and computer use provider for agents.",
|
||||||
@@ -19660,7 +19658,6 @@ const docTemplate = `{
|
|||||||
"Enables embedded NATS pubsub.",
|
"Enables embedded NATS pubsub.",
|
||||||
"Allows organizations to deviate from the default organization-member roles, in support of Gateway Accounts.",
|
"Allows organizations to deviate from the default organization-member roles, in support of Gateway Accounts.",
|
||||||
"Enables AI Gateway cost control functionality.",
|
"Enables AI Gateway cost control functionality.",
|
||||||
"Enables workspace-app and port preview tabs in the Coder Agents right panel.",
|
|
||||||
"Enables the advisor tool for root agent chats.",
|
"Enables the advisor tool for root agent chats.",
|
||||||
"Enables virtual desktop and computer use provider for agents."
|
"Enables virtual desktop and computer use provider for agents."
|
||||||
],
|
],
|
||||||
@@ -19675,7 +19672,6 @@ const docTemplate = `{
|
|||||||
"ExperimentNATSPubsub",
|
"ExperimentNATSPubsub",
|
||||||
"ExperimentMinimumImplicitMember",
|
"ExperimentMinimumImplicitMember",
|
||||||
"ExperimentAIGatewayCostControl",
|
"ExperimentAIGatewayCostControl",
|
||||||
"ExperimentAgentAppTabs",
|
|
||||||
"ExperimentChatAdvisor",
|
"ExperimentChatAdvisor",
|
||||||
"ExperimentChatVirtualDesktop"
|
"ExperimentChatVirtualDesktop"
|
||||||
]
|
]
|
||||||
|
|||||||
Generated
-4
@@ -17823,13 +17823,11 @@
|
|||||||
"nats_pubsub",
|
"nats_pubsub",
|
||||||
"minimum-implicit-member",
|
"minimum-implicit-member",
|
||||||
"ai-gateway-cost-control",
|
"ai-gateway-cost-control",
|
||||||
"agent-app-tabs",
|
|
||||||
"chat-advisor",
|
"chat-advisor",
|
||||||
"chat-virtual-desktop"
|
"chat-virtual-desktop"
|
||||||
],
|
],
|
||||||
"x-enum-comments": {
|
"x-enum-comments": {
|
||||||
"ExperimentAIGatewayCostControl": "Enables AI Gateway cost control functionality.",
|
"ExperimentAIGatewayCostControl": "Enables AI Gateway cost control functionality.",
|
||||||
"ExperimentAgentAppTabs": "Enables workspace-app and port preview tabs in the Coder Agents right panel.",
|
|
||||||
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
||||||
"ExperimentChatAdvisor": "Enables the advisor tool for root agent chats.",
|
"ExperimentChatAdvisor": "Enables the advisor tool for root agent chats.",
|
||||||
"ExperimentChatVirtualDesktop": "Enables virtual desktop and computer use provider for agents.",
|
"ExperimentChatVirtualDesktop": "Enables virtual desktop and computer use provider for agents.",
|
||||||
@@ -17853,7 +17851,6 @@
|
|||||||
"Enables embedded NATS pubsub.",
|
"Enables embedded NATS pubsub.",
|
||||||
"Allows organizations to deviate from the default organization-member roles, in support of Gateway Accounts.",
|
"Allows organizations to deviate from the default organization-member roles, in support of Gateway Accounts.",
|
||||||
"Enables AI Gateway cost control functionality.",
|
"Enables AI Gateway cost control functionality.",
|
||||||
"Enables workspace-app and port preview tabs in the Coder Agents right panel.",
|
|
||||||
"Enables the advisor tool for root agent chats.",
|
"Enables the advisor tool for root agent chats.",
|
||||||
"Enables virtual desktop and computer use provider for agents."
|
"Enables virtual desktop and computer use provider for agents."
|
||||||
],
|
],
|
||||||
@@ -17868,7 +17865,6 @@
|
|||||||
"ExperimentNATSPubsub",
|
"ExperimentNATSPubsub",
|
||||||
"ExperimentMinimumImplicitMember",
|
"ExperimentMinimumImplicitMember",
|
||||||
"ExperimentAIGatewayCostControl",
|
"ExperimentAIGatewayCostControl",
|
||||||
"ExperimentAgentAppTabs",
|
|
||||||
"ExperimentChatAdvisor",
|
"ExperimentChatAdvisor",
|
||||||
"ExperimentChatVirtualDesktop"
|
"ExperimentChatVirtualDesktop"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5197,7 +5197,6 @@ const (
|
|||||||
ExperimentNATSPubsub Experiment = "nats_pubsub" // Enables embedded NATS pubsub.
|
ExperimentNATSPubsub Experiment = "nats_pubsub" // Enables embedded NATS pubsub.
|
||||||
ExperimentMinimumImplicitMember Experiment = "minimum-implicit-member" // Allows organizations to deviate from the default organization-member roles, in support of Gateway Accounts.
|
ExperimentMinimumImplicitMember Experiment = "minimum-implicit-member" // Allows organizations to deviate from the default organization-member roles, in support of Gateway Accounts.
|
||||||
ExperimentAIGatewayCostControl Experiment = "ai-gateway-cost-control" // Enables AI Gateway cost control functionality.
|
ExperimentAIGatewayCostControl Experiment = "ai-gateway-cost-control" // Enables AI Gateway cost control functionality.
|
||||||
ExperimentAgentAppTabs Experiment = "agent-app-tabs" // Enables workspace-app and port preview tabs in the Coder Agents right panel.
|
|
||||||
ExperimentChatAdvisor Experiment = "chat-advisor" // Enables the advisor tool for root agent chats.
|
ExperimentChatAdvisor Experiment = "chat-advisor" // Enables the advisor tool for root agent chats.
|
||||||
ExperimentChatVirtualDesktop Experiment = "chat-virtual-desktop" // Enables virtual desktop and computer use provider for agents.
|
ExperimentChatVirtualDesktop Experiment = "chat-virtual-desktop" // Enables virtual desktop and computer use provider for agents.
|
||||||
)
|
)
|
||||||
@@ -5224,8 +5223,6 @@ func (e Experiment) DisplayName() string {
|
|||||||
return "Gateway Accounts (minimum implicit member)"
|
return "Gateway Accounts (minimum implicit member)"
|
||||||
case ExperimentAIGatewayCostControl:
|
case ExperimentAIGatewayCostControl:
|
||||||
return "AI Gateway Cost Control"
|
return "AI Gateway Cost Control"
|
||||||
case ExperimentAgentAppTabs:
|
|
||||||
return "Coder Agents App and Port Tabs"
|
|
||||||
case ExperimentChatAdvisor:
|
case ExperimentChatAdvisor:
|
||||||
return "Chat Advisor"
|
return "Chat Advisor"
|
||||||
case ExperimentChatVirtualDesktop:
|
case ExperimentChatVirtualDesktop:
|
||||||
@@ -5250,7 +5247,6 @@ var ExperimentsKnown = Experiments{
|
|||||||
ExperimentWorkspaceBuildUpdates,
|
ExperimentWorkspaceBuildUpdates,
|
||||||
ExperimentMinimumImplicitMember,
|
ExperimentMinimumImplicitMember,
|
||||||
ExperimentAIGatewayCostControl,
|
ExperimentAIGatewayCostControl,
|
||||||
ExperimentAgentAppTabs,
|
|
||||||
ExperimentChatAdvisor,
|
ExperimentChatAdvisor,
|
||||||
ExperimentChatVirtualDesktop,
|
ExperimentChatVirtualDesktop,
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+3
-3
@@ -7087,9 +7087,9 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
|||||||
|
|
||||||
#### Enumerated Values
|
#### Enumerated Values
|
||||||
|
|
||||||
| Value(s) |
|
| Value(s) |
|
||||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `agent-app-tabs`, `ai-gateway-cost-control`, `auto-fill-parameters`, `chat-advisor`, `chat-virtual-desktop`, `example`, `mcp-server-http`, `minimum-implicit-member`, `nats_pubsub`, `notifications`, `oauth2`, `workspace-build-updates`, `workspace-usage` |
|
| `ai-gateway-cost-control`, `auto-fill-parameters`, `chat-advisor`, `chat-virtual-desktop`, `example`, `mcp-server-http`, `minimum-implicit-member`, `nats_pubsub`, `notifications`, `oauth2`, `workspace-build-updates`, `workspace-usage` |
|
||||||
|
|
||||||
## codersdk.ExternalAPIKeyScopes
|
## codersdk.ExternalAPIKeyScopes
|
||||||
|
|
||||||
|
|||||||
Generated
-2
@@ -4586,7 +4586,6 @@ export const EntitlementsWarningHeader = "X-Coder-Entitlements-Warning";
|
|||||||
// From codersdk/deployment.go
|
// From codersdk/deployment.go
|
||||||
export type Experiment =
|
export type Experiment =
|
||||||
| "ai-gateway-cost-control"
|
| "ai-gateway-cost-control"
|
||||||
| "agent-app-tabs"
|
|
||||||
| "auto-fill-parameters"
|
| "auto-fill-parameters"
|
||||||
| "chat-advisor"
|
| "chat-advisor"
|
||||||
| "chat-virtual-desktop"
|
| "chat-virtual-desktop"
|
||||||
@@ -4601,7 +4600,6 @@ export type Experiment =
|
|||||||
|
|
||||||
export const Experiments: Experiment[] = [
|
export const Experiments: Experiment[] = [
|
||||||
"ai-gateway-cost-control",
|
"ai-gateway-cost-control",
|
||||||
"agent-app-tabs",
|
|
||||||
"auto-fill-parameters",
|
"auto-fill-parameters",
|
||||||
"chat-advisor",
|
"chat-advisor",
|
||||||
"chat-virtual-desktop",
|
"chat-virtual-desktop",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { useProxy } from "#/contexts/ProxyContext";
|
|||||||
import { isWorkspaceAppEmbeddable } from "#/modules/apps/apps";
|
import { isWorkspaceAppEmbeddable } from "#/modules/apps/apps";
|
||||||
import { WorkspaceAppFrame } from "#/modules/apps/WorkspaceAppFrame";
|
import { WorkspaceAppFrame } from "#/modules/apps/WorkspaceAppFrame";
|
||||||
import { findWorkspaceAppWithAgent } from "#/modules/apps/workspaceApps";
|
import { findWorkspaceAppWithAgent } from "#/modules/apps/workspaceApps";
|
||||||
import { useDashboard } from "#/modules/dashboard/useDashboard";
|
|
||||||
import { cn } from "#/utils/cn";
|
import { cn } from "#/utils/cn";
|
||||||
import { pageTitle } from "#/utils/page";
|
import { pageTitle } from "#/utils/page";
|
||||||
import { findWorkspaceAgent } from "#/utils/workspace";
|
import { findWorkspaceAgent } from "#/utils/workspace";
|
||||||
@@ -387,7 +386,6 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { proxy } = useProxy();
|
const { proxy } = useProxy();
|
||||||
const wildcardHostname = proxy.preferredWildcardHostname;
|
const wildcardHostname = proxy.preferredWildcardHostname;
|
||||||
const { experiments } = useDashboard();
|
|
||||||
|
|
||||||
const canOpenChatSharing = canShareChat && organizationId !== undefined;
|
const canOpenChatSharing = canShareChat && organizationId !== undefined;
|
||||||
|
|
||||||
@@ -494,19 +492,10 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
|
|||||||
const availableDesktopChatId =
|
const availableDesktopChatId =
|
||||||
workspace && workspaceAgent ? desktopChatId : undefined;
|
workspace && workspaceAgent ? desktopChatId : undefined;
|
||||||
|
|
||||||
// Workspace app and port preview tabs are gated behind the agent-app-tabs
|
|
||||||
// experiment. Terminal tabs are generally available. Derived after all hook
|
|
||||||
// calls so the React Compiler keeps the tab list and the tab-open handlers
|
|
||||||
// below in a single memoization scope.
|
|
||||||
const userAppTabsEnabled = experiments.includes("agent-app-tabs");
|
|
||||||
|
|
||||||
// When app and port tabs are gated off, persisted tabs of those kinds
|
|
||||||
// are hidden rather than deleted; the save effect persists the raw tab
|
|
||||||
// state, so they reappear if the gate lifts.
|
|
||||||
const validatedUserRightPanelTabs = validateUserRightPanelTabs(
|
const validatedUserRightPanelTabs = validateUserRightPanelTabs(
|
||||||
userRightPanelTabs,
|
userRightPanelTabs,
|
||||||
{ workspace, workspaceAgent, wildcardHostname },
|
{ workspace, workspaceAgent, wildcardHostname },
|
||||||
).filter((tab) => userAppTabsEnabled || tab.kind === "terminal");
|
);
|
||||||
|
|
||||||
const hasBuiltInTerminal = Boolean(
|
const hasBuiltInTerminal = Boolean(
|
||||||
workspace && workspaceAgent && !defaultTerminalHidden,
|
workspace && workspaceAgent && !defaultTerminalHidden,
|
||||||
@@ -1001,7 +990,6 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
|
|||||||
tabs={sidebarTabs}
|
tabs={sidebarTabs}
|
||||||
addTabControl={
|
addTabControl={
|
||||||
<RightPanelAddTabControl
|
<RightPanelAddTabControl
|
||||||
appExperimentEnabled={userAppTabsEnabled}
|
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
agent={workspaceAgent}
|
agent={workspaceAgent}
|
||||||
host={wildcardHostname}
|
host={wildcardHostname}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ const meta = {
|
|||||||
title: "pages/AgentsPage/components/RightPanel/RightPanelAddTabControl",
|
title: "pages/AgentsPage/components/RightPanel/RightPanelAddTabControl",
|
||||||
component: RightPanelAddTabControl,
|
component: RightPanelAddTabControl,
|
||||||
args: {
|
args: {
|
||||||
appExperimentEnabled: true,
|
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
agent: {
|
agent: {
|
||||||
...MockWorkspaceAgent,
|
...MockWorkspaceAgent,
|
||||||
@@ -176,14 +175,3 @@ export const DisconnectedWorkspace: Story = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppExperimentDisabled: Story = {
|
|
||||||
args: {
|
|
||||||
appExperimentEnabled: false,
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
await expect(canvas.getByLabelText("New terminal tab")).toBeEnabled();
|
|
||||||
await expect(canvas.queryByLabelText("Add panel")).not.toBeInTheDocument();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ const AgentPortsSubMenu: FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RightPanelAddTabControl: FC<{
|
export const RightPanelAddTabControl: FC<{
|
||||||
appExperimentEnabled: boolean;
|
|
||||||
workspace?: Workspace;
|
workspace?: Workspace;
|
||||||
agent?: WorkspaceAgent;
|
agent?: WorkspaceAgent;
|
||||||
host?: string;
|
host?: string;
|
||||||
@@ -69,7 +68,6 @@ export const RightPanelAddTabControl: FC<{
|
|||||||
onOpenCommandApp?: (app: WorkspaceApp) => void;
|
onOpenCommandApp?: (app: WorkspaceApp) => void;
|
||||||
onOpenPort?: (selection: PortSelection) => void;
|
onOpenPort?: (selection: PortSelection) => void;
|
||||||
}> = ({
|
}> = ({
|
||||||
appExperimentEnabled,
|
|
||||||
workspace,
|
workspace,
|
||||||
agent,
|
agent,
|
||||||
host = "",
|
host = "",
|
||||||
@@ -93,119 +91,114 @@ export const RightPanelAddTabControl: FC<{
|
|||||||
disabled={!canCreateTerminal}
|
disabled={!canCreateTerminal}
|
||||||
aria-label="New terminal tab"
|
aria-label="New terminal tab"
|
||||||
title="New terminal tab"
|
title="New terminal tab"
|
||||||
className={cn(
|
className="size-6 rounded-none border-0 bg-transparent p-0 text-content-secondary hover:bg-surface-secondary hover:text-content-primary border-r border-solid border-border-default"
|
||||||
"size-6 rounded-none border-0 bg-transparent p-0 text-content-secondary hover:bg-surface-secondary hover:text-content-primary",
|
|
||||||
appExperimentEnabled && "border-r border-solid border-border-default",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<PlusIcon className="size-3.5" />
|
<PlusIcon className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
{appExperimentEnabled && (
|
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
<DropdownMenuTrigger asChild>
|
||||||
<DropdownMenuTrigger asChild>
|
<Button
|
||||||
<Button
|
variant="subtle"
|
||||||
variant="subtle"
|
size="icon"
|
||||||
size="icon"
|
aria-label="Add panel"
|
||||||
aria-label="Add panel"
|
className="size-6 rounded-none border-0 bg-transparent p-0 text-content-secondary hover:bg-surface-secondary hover:text-content-primary"
|
||||||
className="size-6 rounded-none border-0 bg-transparent p-0 text-content-secondary hover:bg-surface-secondary hover:text-content-primary"
|
|
||||||
>
|
|
||||||
<ChevronDownIcon
|
|
||||||
className={cn(
|
|
||||||
"size-3 transition-transform",
|
|
||||||
open && "rotate-180",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
align="end"
|
|
||||||
side="bottom"
|
|
||||||
className="w-52 p-1 [&_[role=menuitem]]:py-1 [&_[role=menuitem]]:text-xs [&_img]:!size-3.5 [&_svg]:!size-3.5"
|
|
||||||
>
|
>
|
||||||
<DropdownMenuItem
|
<ChevronDownIcon
|
||||||
onSelect={onNewTerminal}
|
className={cn(
|
||||||
disabled={!canCreateTerminal}
|
"size-3 transition-transform",
|
||||||
>
|
open && "rotate-180",
|
||||||
<SquareTerminalIcon />
|
)}
|
||||||
New Terminal
|
/>
|
||||||
</DropdownMenuItem>
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
align="end"
|
||||||
|
side="bottom"
|
||||||
|
className="w-52 p-1 [&_[role=menuitem]]:py-1 [&_[role=menuitem]]:text-xs [&_img]:!size-3.5 [&_svg]:!size-3.5"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={onNewTerminal}
|
||||||
|
disabled={!canCreateTerminal}
|
||||||
|
>
|
||||||
|
<SquareTerminalIcon />
|
||||||
|
New Terminal
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
{workspace && agent && userApps.length > 0 && (
|
{workspace && agent && userApps.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSeparator className="my-1" />
|
<DropdownMenuSeparator className="my-1" />
|
||||||
{userApps.map((app) => {
|
{userApps.map((app) => {
|
||||||
if (app.command && onOpenCommandApp) {
|
if (app.command && onOpenCommandApp) {
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={app.id}
|
|
||||||
onSelect={() => onOpenCommandApp(app)}
|
|
||||||
disabled={!isRunning}
|
|
||||||
>
|
|
||||||
{app.icon ? (
|
|
||||||
<ExternalImage
|
|
||||||
src={app.icon}
|
|
||||||
alt=""
|
|
||||||
className="rounded-sm"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<SquareTerminalIcon />
|
|
||||||
)}
|
|
||||||
{app.display_name ?? app.slug}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (isWorkspaceAppEmbeddable(app) && onOpenWorkspaceApp) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={app.id}
|
|
||||||
onSelect={() => onOpenWorkspaceApp(app)}
|
|
||||||
disabled={!isRunning}
|
|
||||||
>
|
|
||||||
{app.icon ? (
|
|
||||||
<ExternalImage
|
|
||||||
src={app.icon}
|
|
||||||
alt=""
|
|
||||||
className="rounded-sm"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<LayoutGridIcon />
|
|
||||||
)}
|
|
||||||
{app.display_name ?? app.slug}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<AppLink
|
<DropdownMenuItem
|
||||||
key={app.id}
|
key={app.id}
|
||||||
workspace={workspace}
|
onSelect={() => onOpenCommandApp(app)}
|
||||||
agent={agent}
|
disabled={!isRunning}
|
||||||
app={app}
|
>
|
||||||
grouped
|
{app.icon ? (
|
||||||
/>
|
<ExternalImage
|
||||||
|
src={app.icon}
|
||||||
|
alt=""
|
||||||
|
className="rounded-sm"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SquareTerminalIcon />
|
||||||
|
)}
|
||||||
|
{app.display_name ?? app.slug}
|
||||||
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
})}
|
}
|
||||||
</>
|
if (isWorkspaceAppEmbeddable(app) && onOpenWorkspaceApp) {
|
||||||
)}
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
{workspace &&
|
key={app.id}
|
||||||
agent &&
|
onSelect={() => onOpenWorkspaceApp(app)}
|
||||||
onOpenPort &&
|
disabled={!isRunning}
|
||||||
canShowPortForwarding(agent, host) && (
|
>
|
||||||
<>
|
{app.icon ? (
|
||||||
<DropdownMenuSeparator className="my-1" />
|
<ExternalImage
|
||||||
<AgentPortsSubMenu
|
src={app.icon}
|
||||||
|
alt=""
|
||||||
|
className="rounded-sm"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<LayoutGridIcon />
|
||||||
|
)}
|
||||||
|
{app.display_name ?? app.slug}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AppLink
|
||||||
|
key={app.id}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
agent={agent}
|
agent={agent}
|
||||||
host={host}
|
app={app}
|
||||||
isOpen={open}
|
grouped
|
||||||
isRunning={isRunning}
|
|
||||||
onPortSelect={onOpenPort}
|
|
||||||
/>
|
/>
|
||||||
</>
|
);
|
||||||
)}
|
})}
|
||||||
</DropdownMenuContent>
|
</>
|
||||||
</DropdownMenu>
|
)}
|
||||||
)}
|
|
||||||
|
{workspace &&
|
||||||
|
agent &&
|
||||||
|
onOpenPort &&
|
||||||
|
canShowPortForwarding(agent, host) && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator className="my-1" />
|
||||||
|
<AgentPortsSubMenu
|
||||||
|
workspace={workspace}
|
||||||
|
agent={agent}
|
||||||
|
host={host}
|
||||||
|
isOpen={open}
|
||||||
|
isRunning={isRunning}
|
||||||
|
onPortSelect={onOpenPort}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user