diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-container-config-card.tsx b/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-container-config-card.tsx deleted file mode 100644 index b9b1bbc8..00000000 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-container-config-card.tsx +++ /dev/null @@ -1,178 +0,0 @@ -'use client'; - -import type { z } from "zod"; -import { useActionState, useEffect } from "react"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import ContainerCommandArgsFields from "@/components/custom/container-command-args-fields"; -import { SubmitButton } from "@/components/custom/submit-button"; -import { DrawerCard, DrawerCardContent, DrawerCardDescription, DrawerCardFooter, DrawerCardHeader, DrawerCardTitle } from "@/components/custom/drawer-card"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Switch } from "@/components/ui/switch"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { FormUtils } from "@/frontend/utils/form.utilts"; -import { AgentExtendedModel } from "@/shared/model/agent-extended.model"; -import { - agentContainerConfigZodModel, - AgentContainerConfigModel, -} from "@/shared/model/agent-config.model"; -import { ServerActionResult } from "@/shared/model/server-action-error-return.model"; -import { saveAgentContainerConfig } from "./actions"; -import { ContainerCommangArgsUtils } from "@/shared/utils/container-command-args.utils"; - -const DEFAULT_RUNTIME_CLASS = '__quickstack_default__'; - -export default function AgentContainerConfigCard({ agent, readonly, runtimeClasses }: { - agent: AgentExtendedModel; - readonly: boolean; - runtimeClasses: string[]; -}) { - const inputValue = (value: unknown) => typeof value === 'string' || typeof value === 'number' ? value : ''; - const form = useForm, unknown, z.output>({ - resolver: zodResolver(agentContainerConfigZodModel), - defaultValues: { - containerCommand: ContainerCommangArgsUtils.parseStoredContainerCommandItems(agent.containerCommand), - containerArgs: agent.containerArgs - ? JSON.parse(agent.containerArgs).map((arg: string) => ({ value: arg })) - : [], - workingDir: agent.workingDir ?? '', - runtimeClassName: agent.runtimeClassName ?? '', - deployFileBrowser: agent.deployFileBrowser, - }, - disabled: readonly, - }); - - const [state, formAction] = useActionState( - (state: ServerActionResult, payload: AgentContainerConfigModel) => - saveAgentContainerConfig(state, payload, agent.id), - FormUtils.getInitialFormState(), - ); - - useEffect(() => { - if (state.status === 'success') { - toast.success('Container configuration saved. Click "Deploy" to apply changes.'); - } - FormUtils.mapValidationErrorsToForm(state, form); - }, [form, state]); - - return ( -
- - form.handleSubmit((data) => formAction(data))()}> - - - Container Configuration - - Configure agent sandbox startup. - - - -
-
-

Runtime

-

- Leave command and arguments empty to use the QuickStack default container startup. -

-
- -
- - ( - - Runtime Class - -

- Selects the Kubernetes RuntimeClass for new agent sandboxes. -

- -
- )} - /> - - ( - - Working Directory - - - - - - )} - /> - - ( - -
- Deploy File Browser -

- Adds a File Browser sidecar pod to each agent sandbox. -

-
- - - -
- )} - /> -
- {!readonly && ( - - Save - {state?.status === 'error' && !state?.errors && ( -

{state.message}

- )} -
- )} -
- -
- - ); -} diff --git a/src/app/project/[projectId]/agent-components/create-agent-dialog.tsx b/src/app/project/[projectId]/agents/create/create-agent-dialog.tsx similarity index 99% rename from src/app/project/[projectId]/agent-components/create-agent-dialog.tsx rename to src/app/project/[projectId]/agents/create/create-agent-dialog.tsx index fae2131b..cfb4d63a 100644 --- a/src/app/project/[projectId]/agent-components/create-agent-dialog.tsx +++ b/src/app/project/[projectId]/agents/create/create-agent-dialog.tsx @@ -20,7 +20,7 @@ import { import SelectBox from "@/components/custom/multiselect-field"; import { Toast } from "@/frontend/utils/toast.utils"; import { Actions } from "@/frontend/utils/nextjs-actions.utils"; -import { createAgent, getLlmGateways, getModelAliasesForGateway } from "../actions"; +import { createAgent, getLlmGateways, getModelAliasesForGateway } from "../../actions"; import { useRouter } from "next/navigation"; import { PathBuilderUtils } from "@/shared/utils/path-builder.utils"; import { Loader2 } from "lucide-react"; diff --git a/src/app/project/[projectId]/agent-components/create-template-agent-setup-dialog.tsx b/src/app/project/[projectId]/agents/create/create-template-agent-setup-dialog.tsx similarity index 99% rename from src/app/project/[projectId]/agent-components/create-template-agent-setup-dialog.tsx rename to src/app/project/[projectId]/agents/create/create-template-agent-setup-dialog.tsx index bea411cd..42d8fab8 100644 --- a/src/app/project/[projectId]/agent-components/create-template-agent-setup-dialog.tsx +++ b/src/app/project/[projectId]/agents/create/create-template-agent-setup-dialog.tsx @@ -17,9 +17,9 @@ import { Actions } from "@/frontend/utils/nextjs-actions.utils"; import { FormUtils } from "@/frontend/utils/form.utilts"; import { AgentTemplateModel, agentTemplateZodModel } from "@/shared/model/agent-template.model"; import { ServerActionResult } from "@/shared/model/server-action-error-return.model"; -import { createAgentFromTemplate, getLlmGateways, getModelAliasesForGateway } from "../actions"; +import { createAgentFromTemplate, getLlmGateways, getModelAliasesForGateway } from "../../actions"; import { useDialogContext } from "@/frontend/states/dialog-context"; -import { TemplateDetailsPanel } from "../template-details-panel"; +import { TemplateDetailsPanel } from "../../template-details-panel"; interface LlmGatewayOption { id: string; diff --git a/src/app/project/[projectId]/agent-components/rename-agent-dialog.tsx b/src/app/project/[projectId]/agents/create/rename-agent-dialog.tsx similarity index 98% rename from src/app/project/[projectId]/agent-components/rename-agent-dialog.tsx rename to src/app/project/[projectId]/agents/create/rename-agent-dialog.tsx index e137d91a..c066ecd8 100644 --- a/src/app/project/[projectId]/agent-components/rename-agent-dialog.tsx +++ b/src/app/project/[projectId]/agents/create/rename-agent-dialog.tsx @@ -16,7 +16,7 @@ import { useActionState, useEffect } from "react"; import { useForm } from "react-hook-form"; import type { z } from "zod"; import { toast } from "sonner"; -import { renameAgent } from "../actions"; +import { renameAgent } from "../../actions"; import { Agent } from "@prisma/client"; export function RenameAgentDialog({ agent }: { agent: AgentExtendedModel }) { diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/agent-actions.ts b/src/app/project/[projectId]/agents/details/agent-actions.ts similarity index 100% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/agent-actions.ts rename to src/app/project/[projectId]/agents/details/agent-actions.ts diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-drawer.tsx b/src/app/project/[projectId]/agents/details/agent-drawer.tsx similarity index 97% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-drawer.tsx rename to src/app/project/[projectId]/agents/details/agent-drawer.tsx index ff711be7..7f262d20 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-drawer.tsx +++ b/src/app/project/[projectId]/agents/details/agent-drawer.tsx @@ -26,11 +26,11 @@ import { } from '@/components/ui/empty'; import { ScrollArea } from '@/components/ui/scroll-area'; import { useDialog } from '@/frontend/states/zustand.states'; -import { DrawerTabScrollArea } from '@/app/project/[projectId]/app-components/app-drawer-components/app-details-drawer'; -import { NestedDrawerProvider } from '@/app/project/[projectId]/app-components/app-drawer-components/nested-drawer'; +import { DrawerTabScrollArea } from '@/app/project/workload-components/drawer/drawer-tab-scroll-area'; +import { NestedDrawerProvider } from '@/app/project/workload-components/drawer/nested-drawer'; import AgentSandboxesCard from './sandboxes/agent-sandboxes-card'; -import { AgentBuildsGrid } from './agent-builds-grid'; -import { RenameAgentDialog } from '@/app/project/[projectId]/agent-components/rename-agent-dialog'; +import { AgentBuildsGrid } from './builds/agent-builds-grid'; +import { RenameAgentDialog } from '@/app/project/[projectId]/agents/create/rename-agent-dialog'; import { AgentSandboxStatusActions } from './agent-sandbox-status-actions'; import AgentSandboxDrawerSettings from './agent-sandbox-drawer-settings'; import type { AgentExtendedModel } from '@/shared/model/agent-extended.model'; diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-drawer-settings.tsx b/src/app/project/[projectId]/agents/details/agent-sandbox-drawer-settings.tsx similarity index 86% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-drawer-settings.tsx rename to src/app/project/[projectId]/agents/details/agent-sandbox-drawer-settings.tsx index 0767b371..41d3fcb3 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-drawer-settings.tsx +++ b/src/app/project/[projectId]/agents/details/agent-sandbox-drawer-settings.tsx @@ -8,17 +8,17 @@ import { Network, } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { SettingsSection } from '@/app/project/[projectId]/app-components/app-drawer-components/settings-section'; -import { useNestedDrawer } from '@/app/project/[projectId]/app-components/app-drawer-components/nested-drawer'; -import AgentSourceCard from './configuration/agent-source-card'; -import AgentModelConfigurationCard from './configuration/agent-model-configuration-card'; -import { AgentModelConfigurationEditor } from './configuration/agent-model-configuration-editor'; +import { SettingsSection } from '@/app/project/workload-components/drawer/settings-section'; +import { useNestedDrawer } from '@/app/project/workload-components/drawer/nested-drawer'; +import AgentSourceCard from './source/agent-source-card'; +import AgentModelConfigurationCard from './model/agent-model-configuration-card'; +import { AgentModelConfigurationEditor } from './model/agent-model-configuration-editor'; import AgentContainerConfigCard from './configuration/agent-container-config-card'; import AgentRateLimitsCard from './configuration/agent-rate-limits-card'; -import AgentVolumesCard from './configuration/agent-volumes-card'; -import AgentNetworkPolicyCard from './configuration/agent-network-policy-card'; -import AgentEnvVarsCard from './configuration/agent-env-vars-card'; -import HealthCheckSettings from '@/app/project/app/[appId]/advanced/health-check-settings'; +import AgentVolumesCard from './volumes/agent-volumes-card'; +import AgentNetworkPolicyCard from './network/agent-network-policy-card'; +import AgentEnvVarsCard from './environment/agent-env-vars-card'; +import HealthCheckSettings from '@/app/project/workload-components/health-check-settings'; import { saveAgentHealthCheck } from './configuration/actions'; import DomainsCard from '@/components/custom/domains-card'; import FileMountsCard from '@/components/custom/file-mounts-card'; @@ -69,6 +69,7 @@ export default function AgentSandboxDrawerSettings({ onClick={() => openNestedDrawer({ title: 'Advanced container settings', + description: 'Configure agent sandbox startup.', content: , }) }> @@ -88,12 +89,12 @@ export default function AgentSandboxDrawerSettings({ onClick={() => openNestedDrawer({ title: 'Health checks', + description: 'Configure healthchecks so that k3s can automatically monitor when your agent sandbox is fully started up and ready to receive traffic.', content: ( ), }) @@ -121,7 +122,6 @@ export default function AgentSandboxDrawerSettings({ workloadId={agent.id} workloadType="agent" readonly={readonly} - hideCard /> @@ -131,7 +131,6 @@ export default function AgentSandboxDrawerSettings({ workloadId={agent.id} workloadType="agent" readonly={readonly} - hideCard /> diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-status-actions.tsx b/src/app/project/[projectId]/agents/details/agent-sandbox-status-actions.tsx similarity index 100% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-status-actions.tsx rename to src/app/project/[projectId]/agents/details/agent-sandbox-status-actions.tsx diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/agent-builds-grid.tsx b/src/app/project/[projectId]/agents/details/builds/agent-builds-grid.tsx similarity index 97% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/agent-builds-grid.tsx rename to src/app/project/[projectId]/agents/details/builds/agent-builds-grid.tsx index ce4a0b7d..c92e7b84 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/agent-builds-grid.tsx +++ b/src/app/project/[projectId]/agents/details/builds/agent-builds-grid.tsx @@ -27,8 +27,8 @@ import { Item, ItemContent, ItemTitle } from '@/components/ui/item'; import FullLoadingSpinner from '@/components/ui/full-loading-spinnter'; import ShortCommitHash from '@/components/custom/short-commit-hash'; import BuildStatusBadge from '@/app/builds/build-status-badge'; -import { BuildLogsDialogContent } from '@/app/project/app/[appId]/overview/build-logs-overlay'; -import { useNestedDrawer } from '@/app/project/[projectId]/app-components/app-drawer-components/nested-drawer'; +import { BuildLogsDialogContent } from '@/app/project/workload-components/build-logs-dialog-content'; +import { useNestedDrawer } from '@/app/project/workload-components/drawer/nested-drawer'; import { deleteWorkloadBuildAction, getWorkloadBuildsAction } from '@/app/project/actions'; import { usePolling } from '@/frontend/hooks/use-polling'; import { useConfirmDialog } from '@/frontend/states/zustand.states'; diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/actions.ts b/src/app/project/[projectId]/agents/details/configuration/actions.ts similarity index 100% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/actions.ts rename to src/app/project/[projectId]/agents/details/configuration/actions.ts diff --git a/src/app/project/[projectId]/agents/details/configuration/agent-container-config-card.tsx b/src/app/project/[projectId]/agents/details/configuration/agent-container-config-card.tsx new file mode 100644 index 00000000..49827019 --- /dev/null +++ b/src/app/project/[projectId]/agents/details/configuration/agent-container-config-card.tsx @@ -0,0 +1,171 @@ +'use client'; + +import type { z } from "zod"; +import { useActionState, useEffect, useId } from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import ContainerCommandArgsFields from "@/components/custom/container-command-args-fields"; +import { SubmitButton } from "@/components/custom/submit-button"; +import { NestedDrawerFooter } from "../../../../workload-components/drawer/nested-drawer"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { FormUtils } from "@/frontend/utils/form.utilts"; +import { AgentExtendedModel } from "@/shared/model/agent-extended.model"; +import { + agentContainerConfigZodModel, + AgentContainerConfigModel, +} from "@/shared/model/agent-config.model"; +import { ServerActionResult } from "@/shared/model/server-action-error-return.model"; +import { saveAgentContainerConfig } from "./actions"; +import { ContainerCommangArgsUtils } from "@/shared/utils/container-command-args.utils"; + +const DEFAULT_RUNTIME_CLASS = '__quickstack_default__'; + +export default function AgentContainerConfigCard({ agent, readonly, runtimeClasses }: { + agent: AgentExtendedModel; + readonly: boolean; + runtimeClasses: string[]; +}) { + const formId = useId(); + const inputValue = (value: unknown) => typeof value === 'string' || typeof value === 'number' ? value : ''; + const form = useForm, unknown, z.output>({ + resolver: zodResolver(agentContainerConfigZodModel), + defaultValues: { + containerCommand: ContainerCommangArgsUtils.parseStoredContainerCommandItems(agent.containerCommand), + containerArgs: agent.containerArgs + ? JSON.parse(agent.containerArgs).map((arg: string) => ({ value: arg })) + : [], + workingDir: agent.workingDir ?? '', + runtimeClassName: agent.runtimeClassName ?? '', + deployFileBrowser: agent.deployFileBrowser, + }, + disabled: readonly, + }); + + const [state, formAction] = useActionState( + (state: ServerActionResult, payload: AgentContainerConfigModel) => + saveAgentContainerConfig(state, payload, agent.id), + FormUtils.getInitialFormState(), + ); + + useEffect(() => { + if (state.status === 'success') { + toast.success('Container configuration saved. Click "Deploy" to apply changes.'); + } + FormUtils.mapValidationErrorsToForm(state, form); + }, [form, state]); + + return ( +
+ + form.handleSubmit((data) => formAction(data))()}> +
+
+
+

Runtime

+

+ Leave command and arguments empty to use the QuickStack default container startup. +

+
+ +
+ + ( + + Runtime Class + +

+ Selects the Kubernetes RuntimeClass for new agent sandboxes. +

+ +
+ )} + /> + + ( + + Working Directory + + + + + + )} + /> + + ( + +
+ Deploy File Browser +

+ Adds a File Browser sidecar pod to each agent sandbox. +

+
+ + + +
+ )} + /> +
+ {!readonly && ( + + Save + {state?.status === 'error' && !state?.errors && ( +

{state.message}

+ )} +
+ )} + +
+ + ); +} diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-rate-limits-card.tsx b/src/app/project/[projectId]/agents/details/configuration/agent-rate-limits-card.tsx similarity index 100% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-rate-limits-card.tsx rename to src/app/project/[projectId]/agents/details/configuration/agent-rate-limits-card.tsx diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-env-var-edit-overlay.tsx b/src/app/project/[projectId]/agents/details/environment/agent-env-var-edit-overlay.tsx similarity index 98% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-env-var-edit-overlay.tsx rename to src/app/project/[projectId]/agents/details/environment/agent-env-var-edit-overlay.tsx index 0c145bbb..081967fd 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-env-var-edit-overlay.tsx +++ b/src/app/project/[projectId]/agents/details/environment/agent-env-var-edit-overlay.tsx @@ -9,7 +9,7 @@ import { useDialogContext } from "@/frontend/states/dialog-context"; import { FormUtils } from "@/frontend/utils/form.utilts"; import { AgentEnvVarEditModel, agentEnvVarEditZodModel } from "@/shared/model/agent-config.model"; import { ServerActionResult } from "@/shared/model/server-action-error-return.model"; -import { saveAgentEnvVar } from "./actions"; +import { saveAgentEnvVar } from "../configuration/actions"; import { zodResolver } from "@hookform/resolvers/zod"; import { useActionState, useEffect } from "react"; import { useForm } from "react-hook-form"; diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-env-vars-card.tsx b/src/app/project/[projectId]/agents/details/environment/agent-env-vars-card.tsx similarity index 98% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-env-vars-card.tsx rename to src/app/project/[projectId]/agents/details/environment/agent-env-vars-card.tsx index a3f5e556..a7906e57 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-env-vars-card.tsx +++ b/src/app/project/[projectId]/agents/details/environment/agent-env-vars-card.tsx @@ -7,7 +7,7 @@ import { useConfirmDialog, useDialog } from "@/frontend/states/zustand.states"; import { Toast } from "@/frontend/utils/toast.utils"; import { AgentExtendedModel } from "@/shared/model/agent-extended.model"; import { EditIcon, Plus, TrashIcon } from "lucide-react"; -import { deleteAgentEnvVar } from "./actions"; +import { deleteAgentEnvVar } from "../configuration/actions"; import AgentEnvVarEditOverlay from "./agent-env-var-edit-overlay"; function getEnvironmentVariableNames(encryptedEnvVars: string | null | undefined): string[] { diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-model-configuration-card.tsx b/src/app/project/[projectId]/agents/details/model/agent-model-configuration-card.tsx similarity index 100% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-model-configuration-card.tsx rename to src/app/project/[projectId]/agents/details/model/agent-model-configuration-card.tsx diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-model-configuration-editor.tsx b/src/app/project/[projectId]/agents/details/model/agent-model-configuration-editor.tsx similarity index 99% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-model-configuration-editor.tsx rename to src/app/project/[projectId]/agents/details/model/agent-model-configuration-editor.tsx index 1f6deff7..c12dda29 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-model-configuration-editor.tsx +++ b/src/app/project/[projectId]/agents/details/model/agent-model-configuration-editor.tsx @@ -13,7 +13,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; -import { saveAgentModelConfiguration } from "./actions"; +import { saveAgentModelConfiguration } from "../configuration/actions"; import { getLlmGateways, getModelAliasesForGateway } from "@/app/project/[projectId]/actions"; import { AgentExtendedModel } from "@/shared/model/agent-extended.model"; import { LlmGatewayModel } from "@/shared/model/llm-gateway.model"; diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-network-policy-card.tsx b/src/app/project/[projectId]/agents/details/network/agent-network-policy-card.tsx similarity index 99% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-network-policy-card.tsx rename to src/app/project/[projectId]/agents/details/network/agent-network-policy-card.tsx index a0dc68be..82c8f4da 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-network-policy-card.tsx +++ b/src/app/project/[projectId]/agents/details/network/agent-network-policy-card.tsx @@ -11,7 +11,7 @@ import { Toast } from "@/frontend/utils/toast.utils"; import { useConfirmDialog, useDialog } from "@/frontend/states/zustand.states"; import { useState } from "react"; import { AgentExtendedModel } from "@/shared/model/agent-extended.model"; -import { deleteAgentNetworkPolicyEgressRule, saveAgentNetworkPolicySettings } from "./actions"; +import { deleteAgentNetworkPolicyEgressRule, saveAgentNetworkPolicySettings } from "../configuration/actions"; import AgentNetworkPolicyEgressRuleEditOverlay from "./agent-network-policy-egress-rule-edit-overlay"; import { Tooltip, TooltipProvider, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { InternalHostnameUtils } from "@/shared/utils/internal-hostname.utils"; diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-network-policy-egress-rule-edit-overlay.tsx b/src/app/project/[projectId]/agents/details/network/agent-network-policy-egress-rule-edit-overlay.tsx similarity index 99% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-network-policy-egress-rule-edit-overlay.tsx rename to src/app/project/[projectId]/agents/details/network/agent-network-policy-egress-rule-edit-overlay.tsx index 4fbaa5d9..1ae8ddf8 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-network-policy-egress-rule-edit-overlay.tsx +++ b/src/app/project/[projectId]/agents/details/network/agent-network-policy-egress-rule-edit-overlay.tsx @@ -17,7 +17,7 @@ import { agentNetworkPolicyEgressRuleEditZodModel, } from "@/shared/model/agent-network-policy-edit.model"; import { AgentExtendedModel } from "@/shared/model/agent-extended.model"; -import { getAppsForAgentNetworkPolicy, saveAgentNetworkPolicyEgressRule } from "./actions"; +import { getAppsForAgentNetworkPolicy, saveAgentNetworkPolicyEgressRule } from "../configuration/actions"; import { z } from "zod"; type AgentNetworkPolicyRule = NonNullable['rules'][number]; diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/sandboxes/actions.ts b/src/app/project/[projectId]/agents/details/sandboxes/actions.ts similarity index 100% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/sandboxes/actions.ts rename to src/app/project/[projectId]/agents/details/sandboxes/actions.ts diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/sandboxes/agent-access-dialog.tsx b/src/app/project/[projectId]/agents/details/sandboxes/agent-access-dialog.tsx similarity index 100% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/sandboxes/agent-access-dialog.tsx rename to src/app/project/[projectId]/agents/details/sandboxes/agent-access-dialog.tsx diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/sandboxes/agent-sandboxes-card.tsx b/src/app/project/[projectId]/agents/details/sandboxes/agent-sandboxes-card.tsx similarity index 95% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/sandboxes/agent-sandboxes-card.tsx rename to src/app/project/[projectId]/agents/details/sandboxes/agent-sandboxes-card.tsx index c5633dc0..95bc3c20 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/sandboxes/agent-sandboxes-card.tsx +++ b/src/app/project/[projectId]/agents/details/sandboxes/agent-sandboxes-card.tsx @@ -20,7 +20,7 @@ import { EmptyMedia, EmptyTitle, } from "@/components/ui/empty" -import DeploymentStatusBadge from "@/app/project/app/[appId]/overview/deployment-status-badge"; +import DeploymentStatusBadge from "@/app/project/workload-components/deployment-status-badge"; import type { AgentExtendedModel } from "@/shared/model/agent-extended.model"; import { toast } from "sonner"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; @@ -439,17 +439,19 @@ export default function AgentSandboxesCard({ Filter{statusFilter.length > 0 && ` (${statusFilter.length})`} - Status - - {SANDBOX_STATUSES.map((status) => ( - toggleStatusFilter(status, checked)} - > - {SANDBOX_STATUS_LABELS[status]} - - ))} + + Status + + {SANDBOX_STATUSES.map((status) => ( + toggleStatusFilter(status, checked)} + > + {SANDBOX_STATUS_LABELS[status]} + + ))} + diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-source-card.tsx b/src/app/project/[projectId]/agents/details/source/agent-source-card.tsx similarity index 96% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-source-card.tsx rename to src/app/project/[projectId]/agents/details/source/agent-source-card.tsx index 532eb394..015fac23 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-source-card.tsx +++ b/src/app/project/[projectId]/agents/details/source/agent-source-card.tsx @@ -5,9 +5,9 @@ import { DrawerCard, DrawerCardContent, DrawerCardDescription, DrawerCardHeader, import { useDialog } from "@/frontend/states/zustand.states"; import { AgentExtendedModel } from "@/shared/model/agent-extended.model"; import { Container, FileCode2, GitBranch, KeyRound, Link as LinkIcon, LockKeyhole, Package, Server } from "lucide-react"; -import { ReadonlyInfo } from "@/app/project/app/[appId]/general/app-source-wizard/readonly-info"; -import { defaultDockerfilePath, SourceType, sourceTypeLabels } from "@/app/project/app/[appId]/general/app-source-wizard/types"; -import { PublicDeployKeyDialog } from "@/app/project/app/[appId]/general/app-source-wizard/public-deploy-key-dialog"; +import { ReadonlyInfo } from "@/app/project/workload-components/source-wizard/readonly-info"; +import { defaultDockerfilePath, SourceType, sourceTypeLabels } from "@/app/project/workload-components/source-wizard/types"; +import { PublicDeployKeyDialog } from "@/app/project/workload-components/source-wizard/public-deploy-key-dialog"; import { AgentSourceWizardDialog } from "./agent-source-wizard-dialog"; export default function AgentSourceCard({ agent, readonly }: { diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-source-wizard-dialog.tsx b/src/app/project/[projectId]/agents/details/source/agent-source-wizard-dialog.tsx similarity index 94% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-source-wizard-dialog.tsx rename to src/app/project/[projectId]/agents/details/source/agent-source-wizard-dialog.tsx index 27b25fb6..302585a5 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-source-wizard-dialog.tsx +++ b/src/app/project/[projectId]/agents/details/source/agent-source-wizard-dialog.tsx @@ -18,18 +18,18 @@ import { generateOrRegenerateAgentGitSshKey, getAgentGitBranches, saveAgentSource, -} from "./actions"; +} from "../configuration/actions"; import { deployAgent } from "../agent-actions"; import { PathBuilderUtils } from "@/shared/utils/path-builder.utils"; -import { ContainerImageStep } from "@/app/project/app/[appId]/general/app-source-wizard/container-image-step"; -import { DockerfilePathStep } from "@/app/project/app/[appId]/general/app-source-wizard/dockerfile-path-step"; -import { GitBranchStep } from "@/app/project/app/[appId]/general/app-source-wizard/git-branch-step"; -import { GitHttpsUrlStep } from "@/app/project/app/[appId]/general/app-source-wizard/git-https-url-step"; -import { GitSshUrlStep } from "@/app/project/app/[appId]/general/app-source-wizard/git-ssh-url-step"; -import { SourceSummaryStep } from "@/app/project/app/[appId]/general/app-source-wizard/source-summary-step"; -import { SourceTypeStep } from "@/app/project/app/[appId]/general/app-source-wizard/source-type-step"; -import { defaultDockerfilePath, SourceFormPatch, sourceTypeLabels, SourceType, StepId } from "@/app/project/app/[appId]/general/app-source-wizard/types"; -import { WizardProgress } from "@/app/project/app/[appId]/general/app-source-wizard/wizard-progress"; +import { ContainerImageStep } from "@/app/project/workload-components/source-wizard/container-image-step"; +import { DockerfilePathStep } from "@/app/project/workload-components/source-wizard/dockerfile-path-step"; +import { GitBranchStep } from "@/app/project/workload-components/source-wizard/git-branch-step"; +import { GitHttpsUrlStep } from "@/app/project/workload-components/source-wizard/git-https-url-step"; +import { GitSshUrlStep } from "@/app/project/workload-components/source-wizard/git-ssh-url-step"; +import { SourceSummaryStep } from "@/app/project/workload-components/source-wizard/source-summary-step"; +import { SourceTypeStep } from "@/app/project/workload-components/source-wizard/source-type-step"; +import { defaultDockerfilePath, SourceFormPatch, sourceTypeLabels, SourceType, StepId } from "@/app/project/workload-components/source-wizard/types"; +import { WizardProgress } from "@/app/project/workload-components/source-wizard/wizard-progress"; export function AgentSourceWizardDialog({ agent, gitSshPublicKey }: { agent: AgentExtendedModel; diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-volume-edit-overlay.tsx b/src/app/project/[projectId]/agents/details/volumes/agent-volume-edit-overlay.tsx similarity index 99% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-volume-edit-overlay.tsx rename to src/app/project/[projectId]/agents/details/volumes/agent-volume-edit-overlay.tsx index 81783863..83d6d52a 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-volume-edit-overlay.tsx +++ b/src/app/project/[projectId]/agents/details/volumes/agent-volume-edit-overlay.tsx @@ -15,7 +15,7 @@ import { FormUtils } from "@/frontend/utils/form.utilts"; import { ServerActionResult } from "@/shared/model/server-action-error-return.model"; import { AgentVolumeEditModel, agentVolumeEditZodModel } from "@/shared/model/volume-edit.model"; import { useDialogContext } from "@/frontend/states/dialog-context"; -import { saveAgentVolume } from "./actions"; +import { saveAgentVolume } from "../configuration/actions"; import { StorageClassUtils } from "@/shared/utils/storage-class.utils"; import { Constants } from "@/shared/utils/constants"; diff --git a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-volumes-card.tsx b/src/app/project/[projectId]/agents/details/volumes/agent-volumes-card.tsx similarity index 98% rename from src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-volumes-card.tsx rename to src/app/project/[projectId]/agents/details/volumes/agent-volumes-card.tsx index f0c39d32..af5137b7 100644 --- a/src/app/project/[projectId]/agent-components/agent-drawer-components/configuration/agent-volumes-card.tsx +++ b/src/app/project/[projectId]/agents/details/volumes/agent-volumes-card.tsx @@ -9,7 +9,7 @@ import { useConfirmDialog, useDialog } from "@/frontend/states/zustand.states"; import AgentVolumeEditOverlay from "./agent-volume-edit-overlay"; import { AgentVolume } from "@prisma/client"; import { AgentVolumeEditModel } from "@/shared/model/volume-edit.model"; -import { deleteAgentVolume } from "./actions"; +import { deleteAgentVolume } from "../configuration/actions"; import { Constants } from "@/shared/utils/constants"; function formatSize(mb: number): string { diff --git a/src/app/project/[projectId]/app-components/create-template-app-setup-dialog.tsx b/src/app/project/[projectId]/apps/create/create-template-app-setup-dialog.tsx similarity index 98% rename from src/app/project/[projectId]/app-components/create-template-app-setup-dialog.tsx rename to src/app/project/[projectId]/apps/create/create-template-app-setup-dialog.tsx index 6d566b77..97d6bb15 100644 --- a/src/app/project/[projectId]/app-components/create-template-app-setup-dialog.tsx +++ b/src/app/project/[projectId]/apps/create/create-template-app-setup-dialog.tsx @@ -21,10 +21,10 @@ import { SubmitButton } from "@/components/custom/submit-button"; import { ServerActionResult } from "@/shared/model/server-action-error-return.model" import { toast } from "sonner" import { AppTemplateModel, appTemplateZodModel } from "@/shared/model/app-template.model" -import { createAppFromTemplate } from "../actions" +import { createAppFromTemplate } from "../../actions" import { ScrollArea } from "@/components/ui/scroll-area" import { useDialogContext } from "@/frontend/states/dialog-context" -import { TemplateDetailsPanel } from "../template-details-panel" +import { TemplateDetailsPanel } from "../../template-details-panel" export default function CreateTemplateAppSetupDialog({ appTemplate, diff --git a/src/app/project/[projectId]/app-components/edit-app-dialog.tsx b/src/app/project/[projectId]/apps/create/edit-app-dialog.tsx similarity index 97% rename from src/app/project/[projectId]/app-components/edit-app-dialog.tsx rename to src/app/project/[projectId]/apps/create/edit-app-dialog.tsx index eb4e9973..daaf7b8f 100644 --- a/src/app/project/[projectId]/app-components/edit-app-dialog.tsx +++ b/src/app/project/[projectId]/apps/create/edit-app-dialog.tsx @@ -1,7 +1,7 @@ 'use client' import { Toast } from "@/frontend/utils/toast.utils"; -import { createApp } from "../actions"; +import { createApp } from "../../actions"; import { useRouter } from "next/navigation"; import { cloneElement, type MouseEvent, type ReactElement } from "react"; import type { App } from "@prisma/client"; diff --git a/src/app/project/[projectId]/app-components/new-network-policy-explanation-dialog.tsx b/src/app/project/[projectId]/apps/create/new-network-policy-explanation-dialog.tsx similarity index 97% rename from src/app/project/[projectId]/app-components/new-network-policy-explanation-dialog.tsx rename to src/app/project/[projectId]/apps/create/new-network-policy-explanation-dialog.tsx index 2fec01da..19209049 100644 --- a/src/app/project/[projectId]/app-components/new-network-policy-explanation-dialog.tsx +++ b/src/app/project/[projectId]/apps/create/new-network-policy-explanation-dialog.tsx @@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button'; import { DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { useDialogContext } from '@/frontend/states/dialog-context'; import { Toast } from '@/frontend/utils/toast.utils'; -import { acknowledgeNewNetworkPolicyExplanation } from '../actions'; +import { acknowledgeNewNetworkPolicyExplanation } from '../../actions'; export default function NewNetworkPolicyExplanationDialog() { const { closeDialog } = useDialogContext(); diff --git a/src/app/project/app/[appId]/actions.ts b/src/app/project/[projectId]/apps/details/actions.ts similarity index 100% rename from src/app/project/app/[appId]/actions.ts rename to src/app/project/[projectId]/apps/details/actions.ts diff --git a/src/app/project/[projectId]/apps/details/advanced-network-settings.test.ts b/src/app/project/[projectId]/apps/details/advanced-network-settings.test.ts new file mode 100644 index 00000000..744a3de8 --- /dev/null +++ b/src/app/project/[projectId]/apps/details/advanced-network-settings.test.ts @@ -0,0 +1,47 @@ +import React from 'react'; +import { fireEvent, render, screen, cleanup } from '@testing-library/react'; +import { afterEach, expect, it, vi } from 'vitest'; +import { NestedDrawerProvider } from '../../../workload-components/drawer/nested-drawer'; +import { DrawerSettings } from './app-details-settings'; +import { AppDrawerAppContext } from './app-drawer-app-context'; +import { RolePermissionEnum } from '@/shared/model/role-extended.model'; +import type { AppExtendedModel } from '@/shared/model/app-extended.model'; + +vi.mock('@/components/ui/drawer', () => ({ + Drawer: ({ children }: React.PropsWithChildren) => children, + DrawerContent: ({ children }: React.PropsWithChildren) => children, + DrawerDescription: ({ children }: React.PropsWithChildren) => children, + DrawerHeader: ({ children }: React.PropsWithChildren) => children, + DrawerTitle: ({ children }: React.PropsWithChildren) => children, +})); +vi.mock('@/components/ui/scroll-area', () => ({ ScrollArea: ({ children }: React.PropsWithChildren) => children })); +vi.mock('@/app/project/[projectId]/apps/details/network/ports/node-ports-card', () => ({ default: ({ app }: { app: AppExtendedModel }) => React.createElement('div', null, `Ports: ${app.appNodePorts.length}`) })); +vi.mock('@/app/project/[projectId]/apps/details/network/policy/basic-auth', () => ({ default: ({ app }: { app: AppExtendedModel }) => React.createElement('div', null, `Credentials: ${app.appBasicAuths.length}`) })); +vi.mock('@/app/project/[projectId]/apps/details/network/policy/actions', () => ({ saveHealthCheck: vi.fn() })); +vi.mock('@/app/project/workload-components/health-check-settings', () => ({ default: () => null })); +vi.mock('@/app/project/[projectId]/apps/details/network/policy/network-policy', () => ({ default: () => null })); +vi.mock('@/app/project/[projectId]/apps/details/environment/env-edit', () => ({ default: () => null })); +vi.mock('@/app/project/[projectId]/apps/details/configuration/app-container-config', () => ({ default: () => null })); +vi.mock('@/app/project/[projectId]/apps/details/configuration/app-rate-limits', () => ({ default: () => null })); +vi.mock('@/app/project/[projectId]/apps/details/source/app-source', () => ({ default: () => null })); +vi.mock('@/app/project/[projectId]/apps/details/volumes/volumes-list', () => ({ default: () => null })); +vi.mock('@/components/custom/domains-card', () => ({ default: () => null })); +vi.mock('@/components/custom/file-mounts-card', () => ({ default: () => null })); +vi.mock('./environment/app-details-environment', () => ({ DrawerEnvironment: () => null })); + +afterEach(cleanup); + +it('updates network settings in an open drawer when app data changes', () => { + const app = { id: 'app', appNodePorts: [], appBasicAuths: [], appDomains: [], appFileMounts: [] } as unknown as AppExtendedModel; + const view = (value: AppExtendedModel) => React.createElement(AppDrawerAppContext.Provider, { value }, + React.createElement(NestedDrawerProvider, null, + React.createElement(DrawerSettings, { app: value, role: RolePermissionEnum.READWRITE, storageClasses: [] }), + ), + ); + const { rerender } = render(view(app)); + fireEvent.click(screen.getByRole('button', { name: 'Advanced network settings' })); + expect(screen.getByText('Ports: 0')).toBeTruthy(); + rerender(view({ ...app, appNodePorts: [{}], appBasicAuths: [{}] } as AppExtendedModel)); + expect(screen.getByText('Ports: 1')).toBeTruthy(); + expect(screen.getByText('Credentials: 1')).toBeTruthy(); +}); diff --git a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-drawer.tsx b/src/app/project/[projectId]/apps/details/app-details-drawer.tsx similarity index 58% rename from src/app/project/[projectId]/app-components/app-drawer-components/app-details-drawer.tsx rename to src/app/project/[projectId]/apps/details/app-details-drawer.tsx index 7d4e63d3..50726e6a 100644 --- a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-drawer.tsx +++ b/src/app/project/[projectId]/apps/details/app-details-drawer.tsx @@ -1,36 +1,24 @@ 'use client'; -import { type ReactNode, type Ref } from 'react'; +import { type Ref } from 'react'; import { BarChart3, Bot, Boxes, - ExternalLink, - Hammer, Key, - Play, Rocket, Logs as LogsIcon, Pencil, Settings, - Square, X, RotateCwClock, } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui/tooltip'; + + + + import { ScrollArea } from '@/components/ui/scroll-area'; import { Drawer, @@ -47,31 +35,27 @@ import { EmptyMedia, EmptyTitle, } from '@/components/ui/empty'; -import PodStatusIndicator from '@/components/custom/pod-status-indicator'; -import { deploy, startApp, stopApp } from '@/app/project/app/[appId]/actions'; -import { usePodsStatus } from '@/frontend/states/zustand.states'; import { cn } from '@/frontend/utils/utils'; import { AppSourceUtils } from '@/frontend/utils/app-source.utils'; -import { AppLifecycleUtils } from '@/frontend/utils/app-lifecycle.utils'; -import { Toast } from '@/frontend/utils/toast.utils'; import type { AppExtendedModel } from '@/shared/model/app-extended.model'; -import Logs from '@/app/project/app/[appId]/overview/logs'; -import MonitoringTab from '@/app/project/app/[appId]/overview/monitoring-app'; +import Logs from '@/app/project/[projectId]/apps/details/runtime/logs'; +import MonitoringTab from '@/app/project/[projectId]/apps/details/runtime/monitoring-app'; import { RolePermissionEnum } from '@/shared/model/role-extended.model'; -import GeneralAppSource from '@/app/project/app/[appId]/general/app-source'; -import DbCredentials from '@/app/project/app/[appId]/credentials/db-crendentials'; -import DbToolsCard from '@/app/project/app/[appId]/credentials/db-tools'; +import GeneralAppSource from '@/app/project/[projectId]/apps/details/source/app-source'; +import DbCredentials from '@/app/project/[projectId]/apps/details/credentials/db-credentials'; +import DbToolsCard from '@/app/project/[projectId]/apps/details/credentials/db-tools'; import { DrawerSettings } from './app-details-settings'; -import { NestedDrawerProvider } from './nested-drawer'; -import { DrawerBackupsTab } from './app-details-backups-tab'; -import { EditAppDialog } from '@/app/project/[projectId]/app-components/edit-app-dialog'; +import { NestedDrawerProvider } from '../../../workload-components/drawer/nested-drawer'; +import { AppDrawerAppContext } from './app-drawer-app-context'; +import { DrawerBackupsTab } from './volumes/backups/app-details-backups-tab'; +import { EditAppDialog } from '@/app/project/[projectId]/apps/create/edit-app-dialog'; import type { S3Target } from '@prisma/client'; import type { VolumeBackupExtendedModel } from '@/shared/model/volume-backup-extended.model'; import { AppDrawerNavigationUtils, type AppDrawerTab, } from '@/shared/utils/app-drawer-navigation.utils'; -import DrawerDeploymentsTab from './app-details-deployments-tab'; +import DrawerDeploymentsTab from './deployments/app-details-deployments-tab'; export type AppDetailsDrawerNode = { id: string; @@ -81,167 +65,8 @@ export type AppDetailsDrawerNode = { caption?: string; }; -export function DrawerTabScrollArea({ children }: { children: ReactNode }) { - return ( - -
{children}
-
- ); -} - -function AppStatusActions({ - app, - role, -}: { - app: AppExtendedModel; - role?: RolePermissionEnum; -}) { - const deploymentStatus = usePodsStatus( - (state) => state.podsStatus.get(app.id)?.deploymentStatus ?? 'UNKNOWN', - ); - const lifecycle = AppLifecycleUtils.availability(app, role, deploymentStatus); - const openDomain = (domain: AppExtendedModel['appDomains'][number]) => { - const protocol = domain.useSsl ? 'https' : 'http'; - - window.open(`${protocol}://${domain.hostname}`, '_blank'); - }; - - return ( -
- - {(lifecycle.canManage || app.appDomains.length > 0) && ( - -
- {lifecycle.canManage && ( - <> - - void Toast.fromAction(() => deploy(app.id))} - > - - Deploy - - } - /> - Deploy - - {lifecycle.supportsRebuild && ( - - void Toast.fromAction(() => deploy(app.id, true))} - > - - Rebuild - - } - /> - Rebuild - - )} - - void Toast.fromAction(() => startApp(app.id))} - > - - Start - - } - /> - Start - - - void Toast.fromAction(() => stopApp(app.id))} - > - - Stop - - } - /> - Stop - - - )} - {app.appDomains.length === 1 && ( - - openDomain(app.appDomains[0])} - > - - Open domain - - } - /> - Open domain - - )} - {app.appDomains.length > 1 && ( - - - - - } - /> - - {app.appDomains.map((domain) => ( - openDomain(domain)} - > - - {domain.hostname} - - ))} - - - )} -
-
- )} -
- ); -} +import { DrawerTabScrollArea } from '@/app/project/workload-components/drawer/drawer-tab-scroll-area'; +import { AppStatusActions } from './app-status-actions'; export function AppDetailsDrawer({ contentRef, @@ -308,6 +133,7 @@ export function AppDetailsDrawer({ ref={contentRef} className="min-w-0 border border-border/60 data-[swipe-axis=x]:w-[calc(100%-1rem)] sm:data-[swipe-axis=x]:w-1/2 shadow" > +
+ + + Advanced Network Settings + Configure node ports and basic authentication. + + + + + - -
- - Health Checks - - Configure healthchecks so that k3s can automatically monitor when your application is fully started up and ready to receive traffic (In kubernetes terms, startup, readiness and liveness probes). - - - + + + Health Checks + + Configure healthchecks so that k3s can automatically monitor when your application is fully started up and ready to receive traffic. + + + - -
+ +
); } + +function AdvancedNetworkSettings({ readonly }: { readonly: boolean }) { + const app = useContext(AppDrawerAppContext); + if (!app) return null; + + return
+ + +
; +} diff --git a/src/app/project/[projectId]/apps/details/app-drawer-app-context.ts b/src/app/project/[projectId]/apps/details/app-drawer-app-context.ts new file mode 100644 index 00000000..92c88239 --- /dev/null +++ b/src/app/project/[projectId]/apps/details/app-drawer-app-context.ts @@ -0,0 +1,4 @@ +import { createContext } from 'react'; +import type { AppExtendedModel } from '@/shared/model/app-extended.model'; + +export const AppDrawerAppContext = createContext(undefined); diff --git a/src/app/project/[projectId]/apps/details/app-status-actions.tsx b/src/app/project/[projectId]/apps/details/app-status-actions.tsx new file mode 100644 index 00000000..0a88543a --- /dev/null +++ b/src/app/project/[projectId]/apps/details/app-status-actions.tsx @@ -0,0 +1,188 @@ +'use client'; + + +import { + ExternalLink, + Hammer, + Play, + Rocket, + Square, +} from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; + + + + +import PodStatusIndicator from '@/components/custom/pod-status-indicator'; +import { deploy, startApp, stopApp } from '@/app/project/[projectId]/apps/details/actions'; +import { usePodsStatus } from '@/frontend/states/zustand.states'; +import { AppLifecycleUtils } from '@/frontend/utils/app-lifecycle.utils'; +import { Toast } from '@/frontend/utils/toast.utils'; +import type { AppExtendedModel } from '@/shared/model/app-extended.model'; +import { RolePermissionEnum } from '@/shared/model/role-extended.model'; + + + +export function AppStatusActions({ + app, + role, +}: { + app: AppExtendedModel; + role?: RolePermissionEnum; +}) { + const deploymentStatus = usePodsStatus( + (state) => state.podsStatus.get(app.id)?.deploymentStatus ?? 'UNKNOWN', + ); + const lifecycle = AppLifecycleUtils.availability(app, role, deploymentStatus); + const openDomain = (domain: AppExtendedModel['appDomains'][number]) => { + const protocol = domain.useSsl ? 'https' : 'http'; + + window.open(`${protocol}://${domain.hostname}`, '_blank'); + }; + + return ( +
+ + {(lifecycle.canManage || app.appDomains.length > 0) && ( + +
+ {lifecycle.canManage && ( + <> + + void Toast.fromAction(() => deploy(app.id))} + > + + Deploy + + } + /> + Deploy + + {lifecycle.supportsRebuild && ( + + void Toast.fromAction(() => deploy(app.id, true))} + > + + Rebuild + + } + /> + Rebuild + + )} + + void Toast.fromAction(() => startApp(app.id))} + > + + Start + + } + /> + Start + + + void Toast.fromAction(() => stopApp(app.id))} + > + + Stop + + } + /> + Stop + + + )} + {app.appDomains.length === 1 && ( + + openDomain(app.appDomains[0])} + > + + Open domain + + } + /> + Open domain + + )} + {app.appDomains.length > 1 && ( + + + + + } + /> + + {app.appDomains.map((domain) => ( + openDomain(domain)} + > + + {domain.hostname} + + ))} + + + )} +
+
+ )} +
+ ); +} + diff --git a/src/app/project/app/[appId]/general/actions.ts b/src/app/project/[projectId]/apps/details/configuration/actions.ts similarity index 100% rename from src/app/project/app/[appId]/general/actions.ts rename to src/app/project/[projectId]/apps/details/configuration/actions.ts diff --git a/src/app/project/[projectId]/apps/details/configuration/app-container-config.tsx b/src/app/project/[projectId]/apps/details/configuration/app-container-config.tsx new file mode 100644 index 00000000..b3494b36 --- /dev/null +++ b/src/app/project/[projectId]/apps/details/configuration/app-container-config.tsx @@ -0,0 +1,216 @@ +'use client'; + +import { SubmitButton } from "@/components/custom/submit-button"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; +import { FormUtils } from "@/frontend/utils/form.utilts"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { saveGeneralAppContainerConfig } from "./actions"; +import { ServerActionResult } from "@/shared/model/server-action-error-return.model"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { Switch } from "@/components/ui/switch"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { useActionState, useEffect, useId } from "react"; +import { toast } from "sonner"; +import { AppExtendedModel } from "@/shared/model/app-extended.model"; +import { z } from "zod"; +import { appContainerConfigZodModel } from "@/shared/model/app-container-config.model"; +import ContainerCommandArgsFields, { LabelWithHint } from "@/components/custom/container-command-args-fields"; +import { NestedDrawerFooter } from "../../../../workload-components/drawer/nested-drawer"; +import { ContainerCommangArgsUtils } from "@/shared/utils/container-command-args.utils"; + +export type AppContainerConfigInputModel = z.infer; + +export default function GeneralAppContainerConfig({ app, readonly }: { + app: AppExtendedModel; + readonly: boolean; +}) { + const formId = useId(); + const inputValue = (value: unknown) => typeof value === 'string' || typeof value === 'number' ? value : ''; + // Parse containerArgs from JSON string to array + const form = useForm, unknown, z.output>({ + resolver: zodResolver(appContainerConfigZodModel), + defaultValues: { + containerCommand: ContainerCommangArgsUtils.parseStoredContainerCommandItems(app.containerCommand), + containerArgs: app.containerArgs + ? JSON.parse(app.containerArgs).map((arg: string) => ({ value: arg })) + : [], + securityContextRunAsUser: app.securityContextRunAsUser ?? undefined, + securityContextRunAsGroup: app.securityContextRunAsGroup ?? undefined, + securityContextFsGroup: app.securityContextFsGroup ?? undefined, + securityContextPrivileged: app.securityContextPrivileged ?? false, + }, + disabled: readonly, + }); + + const [state, formAction] = useActionState( + (state: ServerActionResult, payload: AppContainerConfigInputModel) => + saveGeneralAppContainerConfig(state, payload, app.id), + FormUtils.getInitialFormState() + ); + + useEffect(() => { + if (state.status === 'success') { + toast.success('Container Configuration Saved', { + description: "Click \"deploy\" to apply the changes to your app.", + }); + } + FormUtils.mapValidationErrorsToForm(state, form) + }, [form, state]); + + const values = form.watch(); + + return ( +
+ + form.handleSubmit((data) => { + return formAction(data); + })()}> +
+
+
+

Runtime

+

+ Leave these fields empty to keep the command and arguments from the container image. +

+
+ + +
+ + + +
+
+

Security Context

+

+ Change these values only when your image, mounted volumes, or tooling require specific Linux permissions. +

+
+ +
+ ( + + + Run As User + + + field.onChange(e.target.value === '' ? null : Number(e.target.value))} + /> + + + + )} + /> + ( + + + Run As Group + + + field.onChange(e.target.value === '' ? null : Number(e.target.value))} + /> + + + + )} + /> + ( + + + FS Group + + + field.onChange(e.target.value === '' ? null : Number(e.target.value))} + /> + + + + )} + /> +
+ + ( + +
+ + + +
+ +

+ Removes most container isolation. The container gets all Linux capabilities, + access to host devices, and can interact with the node almost like a root + process on the host. +

+

+ If the container is compromised, it can affect the Kubernetes node and + other workloads. Use this only for workloads such as Docker-in-Docker + or low-level system tooling. +

+ + )} + > + Privileged Mode +
+ + {values.securityContextPrivileged && + + Enable this only if you fully understand the implications and risks. + + } +
+ +
+ +
+ )} + /> +
+
+ {!readonly && ( + + Save +

{state?.message}

+
+ )} + +
+ + ); +} diff --git a/src/app/project/app/[appId]/general/app-rate-limits.tsx b/src/app/project/[projectId]/apps/details/configuration/app-rate-limits.tsx similarity index 93% rename from src/app/project/app/[appId]/general/app-rate-limits.tsx rename to src/app/project/[projectId]/apps/details/configuration/app-rate-limits.tsx index 290aad00..6bc52457 100644 --- a/src/app/project/app/[appId]/general/app-rate-limits.tsx +++ b/src/app/project/[projectId]/apps/details/configuration/app-rate-limits.tsx @@ -2,7 +2,7 @@ import type { z } from "zod"; import { SubmitButton } from "@/components/custom/submit-button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { CardContent, CardFooter } from "@/components/ui/card"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { FormUtils } from "@/frontend/utils/form.utilts"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -16,16 +16,15 @@ import { useActionState, useEffect, useState } from "react"; import { toast } from "sonner"; import { AppExtendedModel } from "@/shared/model/app-extended.model"; import { cn } from "@/frontend/utils/utils"; -import { getRessourceDataApp } from "../overview/actions"; +import { getRessourceDataApp } from "../deployments/actions"; import { PodsResourceInfoModel } from "@/shared/model/pods-resource-info.model"; import { KubeSizeConverter } from "@/shared/utils/kubernetes-size-converter.utils"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -export default function GeneralAppRateLimits({ app, readonly, hideCard = false }: { +export default function GeneralAppRateLimits({ app, readonly }: { app: AppExtendedModel; readonly: boolean; - hideCard?: boolean; }) { const form = useForm, unknown, z.output>({ resolver: zodResolver(appRateLimitsZodModel), @@ -60,19 +59,15 @@ export default function GeneralAppRateLimits({ app, readonly, hideCard = false } FormUtils.mapValidationErrorsToForm(state, form); }, [form, state]); - const CardWrapper = hideCard ? 'div' : Card; return <> - - {!hideCard && - Container Rate Limits - Provide optional rate Limits per running container instance. - } +
+
form.handleSubmit((data) => { return formAction(data); })()}> - +
- {!readonly && + {!readonly && Save

{state?.message}

} - +
; } diff --git a/src/app/project/app/[appId]/general/app-source-wizard/build-method-step.tsx b/src/app/project/[projectId]/apps/details/configuration/app-source-wizard/build-method-step.tsx similarity index 100% rename from src/app/project/app/[appId]/general/app-source-wizard/build-method-step.tsx rename to src/app/project/[projectId]/apps/details/configuration/app-source-wizard/build-method-step.tsx diff --git a/src/app/project/app/[appId]/credentials/actions.ts b/src/app/project/[projectId]/apps/details/credentials/actions.ts similarity index 100% rename from src/app/project/app/[appId]/credentials/actions.ts rename to src/app/project/[projectId]/apps/details/credentials/actions.ts diff --git a/src/app/project/app/[appId]/credentials/db-crendentials.tsx b/src/app/project/[projectId]/apps/details/credentials/db-credentials.tsx similarity index 100% rename from src/app/project/app/[appId]/credentials/db-crendentials.tsx rename to src/app/project/[projectId]/apps/details/credentials/db-credentials.tsx diff --git a/src/app/project/app/[appId]/credentials/db-gate-db-tool.tsx b/src/app/project/[projectId]/apps/details/credentials/db-gate-db-tool.tsx similarity index 100% rename from src/app/project/app/[appId]/credentials/db-gate-db-tool.tsx rename to src/app/project/[projectId]/apps/details/credentials/db-gate-db-tool.tsx diff --git a/src/app/project/app/[appId]/credentials/db-tools.tsx b/src/app/project/[projectId]/apps/details/credentials/db-tools.tsx similarity index 100% rename from src/app/project/app/[appId]/credentials/db-tools.tsx rename to src/app/project/[projectId]/apps/details/credentials/db-tools.tsx diff --git a/src/app/project/app/[appId]/credentials/phpmyadmin-db-tool.tsx b/src/app/project/[projectId]/apps/details/credentials/phpmyadmin-db-tool.tsx similarity index 100% rename from src/app/project/app/[appId]/credentials/phpmyadmin-db-tool.tsx rename to src/app/project/[projectId]/apps/details/credentials/phpmyadmin-db-tool.tsx diff --git a/src/app/project/app/[appId]/overview/actions.ts b/src/app/project/[projectId]/apps/details/deployments/actions.ts similarity index 100% rename from src/app/project/app/[appId]/overview/actions.ts rename to src/app/project/[projectId]/apps/details/deployments/actions.ts diff --git a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-deployments-tab.tsx b/src/app/project/[projectId]/apps/details/deployments/app-details-deployments-tab.tsx similarity index 79% rename from src/app/project/[projectId]/app-components/app-drawer-components/app-details-deployments-tab.tsx rename to src/app/project/[projectId]/apps/details/deployments/app-details-deployments-tab.tsx index 29293f2f..c49194f3 100644 --- a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-deployments-tab.tsx +++ b/src/app/project/[projectId]/apps/details/deployments/app-details-deployments-tab.tsx @@ -1,11 +1,11 @@ import { AppExtendedModel } from "@/shared/model/app-extended.model"; import { RolePermissionEnum } from "@/shared/model/role-extended.model"; -import { useNestedDrawer } from "./nested-drawer"; +import { useNestedDrawer } from "../../../../workload-components/drawer/nested-drawer"; import { DeploymentInfoModel } from "@/shared/model/deployment-info.model"; -import { BuildLogsDialogContent } from "@/app/project/app/[appId]/overview/build-logs-overlay"; -import BuildsTab from "@/app/project/app/[appId]/overview/deployments"; +import { BuildLogsDialogContent } from "@/app/project/workload-components/build-logs-dialog-content"; +import BuildsTab from "@/app/project/[projectId]/apps/details/deployments/deployments"; import { formatDateTime } from "@/frontend/utils/format.utils"; -import WebhookDeploymentInfo from "@/app/project/app/[appId]/overview/webhook-deployment"; +import WebhookDeploymentInfo from "@/app/project/[projectId]/apps/details/deployments/webhook-deployment"; import { Button } from "@/components/ui/button"; import { Webhook } from "lucide-react"; @@ -51,6 +51,6 @@ export default function DrawerDeploymentsTab({
Latest Deployments
- + ; } \ No newline at end of file diff --git a/src/app/project/app/[appId]/overview/deployments-grid-view.tsx b/src/app/project/[projectId]/apps/details/deployments/deployments-grid-view.tsx similarity index 88% rename from src/app/project/app/[appId]/overview/deployments-grid-view.tsx rename to src/app/project/[projectId]/apps/details/deployments/deployments-grid-view.tsx index 200f2114..7a72cb35 100644 --- a/src/app/project/app/[appId]/overview/deployments-grid-view.tsx +++ b/src/app/project/[projectId]/apps/details/deployments/deployments-grid-view.tsx @@ -8,10 +8,11 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Item, ItemContent, ItemTitle } from '@/components/ui/item'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { formatDateTime } from '@/frontend/utils/format.utils'; import type { DeploymentInfoModel } from '@/shared/model/deployment-info.model'; import { appBuildMethodLabels } from '@/shared/model/app-source-info.model'; -import DeploymentStatusBadge from './deployment-status-badge'; +import DeploymentStatusBadge from '../../../../workload-components/deployment-status-badge'; import { Empty, EmptyDescription, @@ -82,10 +83,15 @@ export function DeploymentsGridView({ )} - + + + onShowLogs(deployment)}> + + Show Deployment Logs + } /> + Show Deployment Logs + + {(canStopBuild(deployment) || canRollback(deployment)) && diff --git a/src/app/project/app/[appId]/overview/deployments.tsx b/src/app/project/[projectId]/apps/details/deployments/deployments.tsx similarity index 71% rename from src/app/project/app/[appId]/overview/deployments.tsx rename to src/app/project/[projectId]/apps/details/deployments/deployments.tsx index ef641101..30fdc19f 100644 --- a/src/app/project/app/[appId]/overview/deployments.tsx +++ b/src/app/project/[projectId]/apps/details/deployments/deployments.tsx @@ -1,14 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; import FullLoadingSpinner from '@/components/ui/full-loading-spinnter'; import { usePolling } from '@/frontend/hooks/use-polling'; -import { useBuildStatus, useConfirmDialog, useDialog } from '@/frontend/states/zustand.states'; +import { useBuildStatus, useConfirmDialog } from '@/frontend/states/zustand.states'; import { Toast } from '@/frontend/utils/toast.utils'; import type { AppExtendedModel } from '@/shared/model/app-extended.model'; import type { DeploymentInfoModel } from '@/shared/model/deployment-info.model'; @@ -19,25 +12,18 @@ import { getDeploymentsAndBuildsForApp, rollbackToDeployment, } from './actions'; -import { BuildLogsDialogContent } from './build-logs-overlay'; -import { DeploymentsDefaultView } from './deployments-default-view'; import { DeploymentsGridView } from './deployments-grid-view'; -export type BuildsTabView = 'default' | 'grid'; - export default function BuildsTab({ app, role, - view = 'default', - onShowLogs: onShowLogsOverride, + onShowLogs, }: { app: AppExtendedModel; role: RolePermissionEnum; - view?: BuildsTabView; - onShowLogs?: (deployment: DeploymentInfoModel) => void; + onShowLogs: (deployment: DeploymentInfoModel) => void; }) { const { openConfirmDialog } = useConfirmDialog(); - const { openDialog } = useDialog(); const [appBuilds, setAppBuilds] = useState(); const [, setError] = useState(); @@ -80,15 +66,6 @@ export default function BuildsTab({ await updateBuilds(); }; - const showLogs = onShowLogsOverride ?? ((deployment: DeploymentInfoModel) => - openDialog( - , - { maxWidth: '1300px' }, - )); const canStopBuild = (deployment: DeploymentInfoModel) => role === RolePermissionEnum.READWRITE && !!deployment.buildJobName @@ -121,28 +98,12 @@ export default function BuildsTab({ deployments: appBuilds ?? [], canStopBuild, canRollback, - onShowLogs: showLogs, + onShowLogs, onStopBuild: (deployment: DeploymentInfoModel) => void stopBuild(deployment), onRollback: (deployment: DeploymentInfoModel) => void rollback(deployment), }; - if (view === 'grid') { - return !appBuilds - ? - : ; - } - - return ( - - - Deployments - - This is an overview of the last deplyoments for this App. - - - - {!appBuilds ? : } - - - ); + return !appBuilds + ? + : ; } diff --git a/src/app/project/app/[appId]/overview/webhook-deployment.tsx b/src/app/project/[projectId]/apps/details/deployments/webhook-deployment.tsx similarity index 100% rename from src/app/project/app/[appId]/overview/webhook-deployment.tsx rename to src/app/project/[projectId]/apps/details/deployments/webhook-deployment.tsx diff --git a/src/app/project/app/[appId]/environment/actions.ts b/src/app/project/[projectId]/apps/details/environment/actions.ts similarity index 100% rename from src/app/project/app/[appId]/environment/actions.ts rename to src/app/project/[projectId]/apps/details/environment/actions.ts diff --git a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-environment.tsx b/src/app/project/[projectId]/apps/details/environment/app-details-environment.tsx similarity index 73% rename from src/app/project/[projectId]/app-components/app-drawer-components/app-details-environment.tsx rename to src/app/project/[projectId]/apps/details/environment/app-details-environment.tsx index 5744297b..f1c9f383 100644 --- a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-environment.tsx +++ b/src/app/project/[projectId]/apps/details/environment/app-details-environment.tsx @@ -3,6 +3,7 @@ import { Copy, Eye, EyeOff } from 'lucide-react'; import { useState } from 'react'; import { Button } from '@/components/ui/button'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import type { AppExtendedModel } from '@/shared/model/app-extended.model'; import { EnvVarUtils } from '@/shared/utils/env-var.utils'; import { toast } from 'sonner'; @@ -60,9 +61,30 @@ function VariableList({ - - {isVisible ? variable.value : '••••••••'} - + + + { + try { + await navigator.clipboard.writeText(variable.value); + toast.success('Variable value copied'); + } catch { + toast.error('Failed to copy variable value'); + } + }} + /> + } + > + {isVisible ? variable.value : '••••••••'} + + Click to copy + + } -
+ ; } diff --git a/src/app/project/app/[appId]/advanced/network-policy-graph.tsx b/src/app/project/[projectId]/apps/details/network/policy/network-policy-graph.tsx similarity index 100% rename from src/app/project/app/[appId]/advanced/network-policy-graph.tsx rename to src/app/project/[projectId]/apps/details/network/policy/network-policy-graph.tsx diff --git a/src/app/project/app/[appId]/advanced/network-policy.tsx b/src/app/project/[projectId]/apps/details/network/policy/network-policy.tsx similarity index 66% rename from src/app/project/app/[appId]/advanced/network-policy.tsx rename to src/app/project/[projectId]/apps/details/network/policy/network-policy.tsx index 04f01a07..84aa710e 100644 --- a/src/app/project/app/[appId]/advanced/network-policy.tsx +++ b/src/app/project/[projectId]/apps/details/network/policy/network-policy.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Info, List, Waypoints } from 'lucide-react'; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; @@ -19,10 +19,11 @@ import { useConfirmDialog, useDialog } from '@/frontend/states/zustand.states'; import AppNetworkPolicyRuleDialog from './app-network-policy-rule-dialog'; import AppNetworkPolicyRuleSection, { AppNetworkPolicyDirection } from './app-network-policy-rule-section'; import NetworkPolicyGraph from './network-policy-graph'; +import { NestedDrawerFooter } from '@/app/project/workload-components/drawer/nested-drawer'; type Project = { id: string; name: string; apps: { id: string; name: string; appType: AppExtendedModel['appType'] }[]; agents: { id: string; name: string }[] }; -export default function NetworkPolicy({ app, readonly, hideCard = false }: { app: AppExtendedModel; readonly: boolean; hideCard?: boolean }) { +export default function NetworkPolicy({ app, readonly }: { app: AppExtendedModel; readonly: boolean; }) { const router = useRouter(); const [draft, setDraft] = useState(() => AppNetworkPolicyDraftUtils.fromApp(app)); const [baseline, setBaseline] = useState(() => AppNetworkPolicyDraftUtils.fromApp(app)); @@ -94,35 +95,31 @@ export default function NetworkPolicy({ app, readonly, hideCard = false }: { app ))} onAdd={addRule} />, { maxWidth: 'max-w-md' }); - const CardWrapper = hideCard ? 'div' : Card; - return - {!hideCard && Network PolicyControl which traffic can reach this app and where it can connect.} - - - - setDraft(current => ({ ...current, useNetworkPolicy }))} /> - - + return
+ + + setDraft(current => ({ ...current, useNetworkPolicy }))} /> + + - {draft.useNetworkPolicy && setView(value as 'rules' | 'graph')}> - - Rules - Network Graph - - - rule.type === 'INGRESS')} readonly={readonly} onAdd={() => openRuleDialog('INGRESS')} onDeleteRule={deleteRule} currentProjectId={app.project.id} projects={projects} /> - rule.type === 'EGRESS')} readonly={readonly} onAdd={() => openRuleDialog('EGRESS')} onDeleteRule={deleteRule} currentProjectId={app.project.id} projects={projects} internetAccess={draft.allowInternetAccess} onInternetAccessChange={changeInternetAccess} /> - - - `${domain.hostname}:${domain.port}`)} projects={projects} /> - - } - - {!readonly && dirty && - - } - ; + {draft.useNetworkPolicy && setView(value as 'rules' | 'graph')}> + + Rules + Network Graph + + + rule.type === 'INGRESS')} readonly={readonly} onAdd={() => openRuleDialog('INGRESS')} onDeleteRule={deleteRule} currentProjectId={app.project.id} projects={projects} /> + rule.type === 'EGRESS')} readonly={readonly} onAdd={() => openRuleDialog('EGRESS')} onDeleteRule={deleteRule} currentProjectId={app.project.id} projects={projects} internetAccess={draft.allowInternetAccess} onInternetAccessChange={changeInternetAccess} /> + + + `${domain.hostname}:${domain.port}`)} projects={projects} /> + + } + {!readonly && dirty && + + } +
; } function SettingRow({ label, description, checked, disabled, onChange, hint }: { label: string; description: string; checked: boolean; disabled: boolean; onChange: (checked: boolean) => void; hint?: string }) { diff --git a/src/app/project/app/[appId]/domains/actions.ts b/src/app/project/[projectId]/apps/details/network/ports/actions.ts similarity index 100% rename from src/app/project/app/[appId]/domains/actions.ts rename to src/app/project/[projectId]/apps/details/network/ports/actions.ts diff --git a/src/app/project/app/[appId]/domains/node-port-edit-dialog.tsx b/src/app/project/[projectId]/apps/details/network/ports/node-port-edit-dialog.tsx similarity index 100% rename from src/app/project/app/[appId]/domains/node-port-edit-dialog.tsx rename to src/app/project/[projectId]/apps/details/network/ports/node-port-edit-dialog.tsx diff --git a/src/app/project/app/[appId]/domains/node-ports.tsx b/src/app/project/[projectId]/apps/details/network/ports/node-ports-card.tsx similarity index 94% rename from src/app/project/app/[appId]/domains/node-ports.tsx rename to src/app/project/[projectId]/apps/details/network/ports/node-ports-card.tsx index 50b792ea..4e1e73fc 100644 --- a/src/app/project/app/[appId]/domains/node-ports.tsx +++ b/src/app/project/[projectId]/apps/details/network/ports/node-ports-card.tsx @@ -11,10 +11,9 @@ import { Toast } from "@/frontend/utils/toast.utils"; import { useConfirmDialog, useDialog } from "@/frontend/states/zustand.states"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -export default function NodePortsCard({ app, readonly, hideCard = false }: { +export default function NodePortsCard({ app, readonly }: { app: AppExtendedModel; readonly: boolean; - hideCard?: boolean; }) { const { openConfirmDialog } = useConfirmDialog(); const { openDialog } = useDialog(); @@ -29,17 +28,16 @@ export default function NodePortsCard({ app, readonly, hideCard = false }: { await Toast.fromAction(() => deleteNodePort(nodePortId)); } }; - const CardWrapper = hideCard ? 'div' : Card; return ( - + Node Ports Expose this app directly on a node/host port, bypassing Traefik. Useful for non-HTTP workloads such as SFTP, game servers, or other TCP/UDP services. - {app.appNodePorts.length > 0 && + {app.appNodePorts.length > 0 && {app.appNodePorts.length} Node Port{app.appNodePorts.length !== 1 ? 's' : ''} @@ -80,7 +78,7 @@ export default function NodePortsCard({ app, readonly, hideCard = false }: {
} {!readonly && ( - + )} -
+ ); } diff --git a/src/app/project/app/[appId]/overview/logs.tsx b/src/app/project/[projectId]/apps/details/runtime/logs.tsx similarity index 86% rename from src/app/project/app/[appId]/overview/logs.tsx rename to src/app/project/[projectId]/apps/details/runtime/logs.tsx index 9a5c4ca9..a160ef44 100644 --- a/src/app/project/app/[appId]/overview/logs.tsx +++ b/src/app/project/[projectId]/apps/details/runtime/logs.tsx @@ -1,8 +1,8 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { CardContent } from "@/components/ui/card"; import { AppExtendedModel } from "@/shared/model/app-extended.model"; -import { Fragment, useEffect, useState } from "react"; -import LogsStreamed from "../../../../../components/custom/logs-streamed"; -import { getPodsForApp as getPodsForAppAction } from "./actions"; +import { useEffect, useState } from "react"; +import LogsStreamed from "../../../../../../components/custom/logs-streamed"; +import { getPodsForApp as getPodsForAppAction } from "../deployments/actions"; import { PodsInfoModel } from "@/shared/model/pods-info.model"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import FullLoadingSpinner from "@/components/ui/full-loading-spinnter"; @@ -26,13 +26,9 @@ import { export default function Logs({ app, role, - hideCard = false, - useFullHeight = false, }: { app: AppExtendedModel; role: RolePermissionEnum; - hideCard?: boolean; - useFullHeight?: boolean; }) { const [selectedPod, setSelectedPod] = useState(undefined); const [appPods, setAppPods] = useState(undefined); @@ -118,20 +114,16 @@ export default function Logs({ } }, [appPods, selectedPod]); - const ContentWrapper = hideCard ? Fragment : Card; const content = <> - {!hideCard && - Logs - Read logs from all running Containers. - } + {!appPods && } {appPods && appPods.length === 0 && ( - + @@ -182,23 +174,15 @@ export default function Logs({ } - {app.projectId && selectedPod &&
+ {app.projectId && selectedPod &&
} ; - if (hideCard && useFullHeight) { - return
{content}
; - } - - return <> - - {content} - - ; + return
{content}
; } diff --git a/src/app/project/app/[appId]/overview/logs.unit.spec.ts b/src/app/project/[projectId]/apps/details/runtime/logs.unit.spec.ts similarity index 94% rename from src/app/project/app/[appId]/overview/logs.unit.spec.ts rename to src/app/project/[projectId]/apps/details/runtime/logs.unit.spec.ts index 48157031..e26fdac8 100644 --- a/src/app/project/app/[appId]/overview/logs.unit.spec.ts +++ b/src/app/project/[projectId]/apps/details/runtime/logs.unit.spec.ts @@ -2,15 +2,15 @@ import React from 'react'; import { act, cleanup, render } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import Logs from './logs'; -import { getPodsForApp } from './actions'; +import { getPodsForApp } from '../deployments/actions'; import { usePodsStatus } from '@/frontend/states/zustand.states'; import { RolePermissionEnum } from '@/shared/model/role-extended.model'; -vi.mock('./actions', () => ({ +vi.mock('../deployments/actions', () => ({ getPodsForApp: vi.fn(), })); -vi.mock('../../../../../components/custom/logs-streamed', () => ({ default: () => null })); +vi.mock('../../../../../../components/custom/logs-streamed', () => ({ default: () => null })); vi.mock('./terminal-overlay', () => ({ TerminalDialog: () => null })); vi.mock('@/components/custom/logs-overlay', () => ({ LogsDialogContent: () => null })); diff --git a/src/app/project/app/[appId]/overview/monitoring-app.tsx b/src/app/project/[projectId]/apps/details/runtime/monitoring-app.tsx similarity index 88% rename from src/app/project/app/[appId]/overview/monitoring-app.tsx rename to src/app/project/[projectId]/apps/details/runtime/monitoring-app.tsx index d4881aa6..52daa810 100644 --- a/src/app/project/app/[appId]/overview/monitoring-app.tsx +++ b/src/app/project/[projectId]/apps/details/runtime/monitoring-app.tsx @@ -1,7 +1,7 @@ -import { Card, CardContent } from "@/components/ui/card"; +import { CardContent } from "@/components/ui/card"; import { AppExtendedModel } from "@/shared/model/app-extended.model"; -import { Fragment, useCallback, useState } from "react"; -import { getRessourceDataApp } from "./actions"; +import { useCallback, useState } from "react"; +import { getRessourceDataApp } from "../deployments/actions"; import FullLoadingSpinner from "@/components/ui/full-loading-spinnter"; import { usePolling } from "@/frontend/hooks/use-polling"; import { PodsResourceInfoModel } from "@/shared/model/pods-resource-info.model"; @@ -12,10 +12,8 @@ import { cn } from "@/frontend/utils/utils"; export default function MonitoringTab({ app, - hideCard = false, }: { app: AppExtendedModel; - hideCard?: boolean; }) { const [selectedPod, setSelectedPod] = useState(undefined); @@ -40,10 +38,8 @@ export default function MonitoringTab({ usePolling(updateValues, { intervalMs: 10000, runImmediately: true }); - const ContentWrapper = hideCard ? Fragment : Card; return <> - - + {!selectedPod ? : @@ -70,6 +66,5 @@ export default function MonitoringTab({
}
-
; } diff --git a/src/app/project/app/[appId]/overview/monitoring-app.unit.spec.ts b/src/app/project/[projectId]/apps/details/runtime/monitoring-app.unit.spec.ts similarity index 96% rename from src/app/project/app/[appId]/overview/monitoring-app.unit.spec.ts rename to src/app/project/[projectId]/apps/details/runtime/monitoring-app.unit.spec.ts index d56ba0f3..f4fa36fd 100644 --- a/src/app/project/app/[appId]/overview/monitoring-app.unit.spec.ts +++ b/src/app/project/[projectId]/apps/details/runtime/monitoring-app.unit.spec.ts @@ -2,9 +2,9 @@ import React from 'react'; import { act, cleanup, render } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import MonitoringTab from './monitoring-app'; -import { getRessourceDataApp } from './actions'; +import { getRessourceDataApp } from '../deployments/actions'; -vi.mock('./actions', () => ({ +vi.mock('../deployments/actions', () => ({ getRessourceDataApp: vi.fn(), })); diff --git a/src/app/project/app/[appId]/overview/terminal-overlay.tsx b/src/app/project/[projectId]/apps/details/runtime/terminal-overlay.tsx similarity index 100% rename from src/app/project/app/[appId]/overview/terminal-overlay.tsx rename to src/app/project/[projectId]/apps/details/runtime/terminal-overlay.tsx diff --git a/src/app/project/app/[appId]/overview/terminal-streamed.tsx b/src/app/project/[projectId]/apps/details/runtime/terminal-streamed.tsx similarity index 100% rename from src/app/project/app/[appId]/overview/terminal-streamed.tsx rename to src/app/project/[projectId]/apps/details/runtime/terminal-streamed.tsx diff --git a/src/app/project/app/[appId]/general/app-source-wizard/app-source-wizard-dialog.tsx b/src/app/project/[projectId]/apps/details/source/app-source-wizard/app-source-wizard-dialog.tsx similarity index 95% rename from src/app/project/app/[appId]/general/app-source-wizard/app-source-wizard-dialog.tsx rename to src/app/project/[projectId]/apps/details/source/app-source-wizard/app-source-wizard-dialog.tsx index d76875e8..13262228 100644 --- a/src/app/project/app/[appId]/general/app-source-wizard/app-source-wizard-dialog.tsx +++ b/src/app/project/[projectId]/apps/details/source/app-source-wizard/app-source-wizard-dialog.tsx @@ -13,19 +13,19 @@ import { ChevronLeft, Loader2, Rocket, Save } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { toast } from "sonner"; -import { detectDockerfilePath, ensureGitSshPublicKey, generateOrRegenerateGitSshKey, getGitBranches, saveGeneralAppSourceInfo } from "../actions"; -import { BuildMethodStep } from "./build-method-step"; -import { ContainerImageStep } from "./container-image-step"; -import { DockerfilePathStep } from "./dockerfile-path-step"; +import { detectDockerfilePath, ensureGitSshPublicKey, generateOrRegenerateGitSshKey, getGitBranches, saveGeneralAppSourceInfo } from "../../configuration/actions"; +import { BuildMethodStep } from "../../configuration/app-source-wizard/build-method-step"; +import { ContainerImageStep } from "../../../../../workload-components/source-wizard/container-image-step"; +import { DockerfilePathStep } from "../../../../../workload-components/source-wizard/dockerfile-path-step"; import { FrameworkConfigurationStep } from "./framework-configuration-step"; import { FrameworkStep } from "./framework-step"; -import { GitBranchStep } from "./git-branch-step"; -import { GitHttpsUrlStep } from "./git-https-url-step"; -import { GitSshUrlStep } from "./git-ssh-url-step"; -import { SourceSummaryStep } from "./source-summary-step"; -import { SourceTypeStep } from "./source-type-step"; -import { defaultDockerfilePath, sourceTypeLabels, SourceType, StepId } from "./types"; -import { WizardProgress } from "./wizard-progress"; +import { GitBranchStep } from "../../../../../workload-components/source-wizard/git-branch-step"; +import { GitHttpsUrlStep } from "../../../../../workload-components/source-wizard/git-https-url-step"; +import { GitSshUrlStep } from "../../../../../workload-components/source-wizard/git-ssh-url-step"; +import { SourceSummaryStep } from "../../../../../workload-components/source-wizard/source-summary-step"; +import { SourceTypeStep } from "../../../../../workload-components/source-wizard/source-type-step"; +import { defaultDockerfilePath, sourceTypeLabels, SourceType, StepId } from "../../../../../workload-components/source-wizard/types"; +import { WizardProgress } from "../../../../../workload-components/source-wizard/wizard-progress"; import { deploy } from "../../actions"; import LoadingSpinner from "@/components/ui/loading-spinner"; diff --git a/src/app/project/app/[appId]/general/app-source-wizard/framework-configuration-step.tsx b/src/app/project/[projectId]/apps/details/source/app-source-wizard/framework-configuration-step.tsx similarity index 95% rename from src/app/project/app/[appId]/general/app-source-wizard/framework-configuration-step.tsx rename to src/app/project/[projectId]/apps/details/source/app-source-wizard/framework-configuration-step.tsx index 38bbb565..79cf9a0c 100644 --- a/src/app/project/app/[appId]/general/app-source-wizard/framework-configuration-step.tsx +++ b/src/app/project/[projectId]/apps/details/source/app-source-wizard/framework-configuration-step.tsx @@ -3,8 +3,8 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component import { JsFramework, jsFrameworkPresets } from "@/shared/model/js-framework.model"; import { ChevronDown, Cpu, Download, FolderOutput, FolderTree, Hammer, Play } from "lucide-react"; import Image from "next/image"; -import { IconInput } from "./source-wizard-fields"; -import { SourceWizardInput } from "./types"; +import { IconInput } from '@/app/project/workload-components/source-wizard/fields/icon-input'; +import { SourceWizardInput } from "../../../../../workload-components/source-wizard/types"; export function FrameworkConfigurationStep({ framework, formData, onChange, onChangeFramework }: { framework: JsFramework; diff --git a/src/app/project/app/[appId]/general/app-source-wizard/framework-step.tsx b/src/app/project/[projectId]/apps/details/source/app-source-wizard/framework-step.tsx similarity index 100% rename from src/app/project/app/[appId]/general/app-source-wizard/framework-step.tsx rename to src/app/project/[projectId]/apps/details/source/app-source-wizard/framework-step.tsx diff --git a/src/app/project/app/[appId]/general/app-source.tsx b/src/app/project/[projectId]/apps/details/source/app-source.tsx similarity index 86% rename from src/app/project/app/[appId]/general/app-source.tsx rename to src/app/project/[projectId]/apps/details/source/app-source.tsx index f0b6a0d7..d4518973 100644 --- a/src/app/project/app/[appId]/general/app-source.tsx +++ b/src/app/project/[projectId]/apps/details/source/app-source.tsx @@ -1,29 +1,25 @@ 'use client'; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { useDialog } from "@/frontend/states/zustand.states"; import { AppExtendedModel } from "@/shared/model/app-extended.model"; import { AppBuildMethod } from "@/shared/model/app-source-info.model"; import { JsFramework, jsFrameworkPresets } from "@/shared/model/js-framework.model"; import { Boxes, Container, FileCode2, GitBranch, KeyRound, Link as LinkIcon, LockKeyhole, Package, Server } from "lucide-react"; import { AppSourceWizardDialog } from "./app-source-wizard/app-source-wizard-dialog"; -import { PublicDeployKeyDialog } from "./app-source-wizard/public-deploy-key-dialog"; -import { ReadonlyInfo } from "./app-source-wizard/readonly-info"; -import { buildMethodLabels, defaultDockerfilePath, SourceType, sourceTypeLabels } from "./app-source-wizard/types"; +import { PublicDeployKeyDialog } from "../../../../workload-components/source-wizard/public-deploy-key-dialog"; +import { ReadonlyInfo } from "../../../../workload-components/source-wizard/readonly-info"; +import { buildMethodLabels, defaultDockerfilePath, SourceType, sourceTypeLabels } from "../../../../workload-components/source-wizard/types"; import { AppSourceUtils } from "@/frontend/utils/app-source.utils"; import FrameworkConfigurationCard from './framework-configuration-card'; export default function GeneralAppSource({ app, readonly, - gitSshPublicKey, - hideCard = false -}: { + gitSshPublicKey }: { app: AppExtendedModel; readonly: boolean; gitSshPublicKey?: string; - hideCard?: boolean }) { const { openDialog } = useDialog(); const configured = AppSourceUtils.isConfiguredSource(app); @@ -47,27 +43,10 @@ export default function GeneralAppSource({ ) ); - if (hideCard) { - return
- {cardContent} - {configured && app.buildMethod === 'FRAMEWORK' && } -
; - } - - return ( - <> - - - Source - Connect the source QuickStack should build or run. - - - {cardContent} - - - {configured && app.buildMethod === 'FRAMEWORK' && } - - ); + return
+ {cardContent} + {configured && app.buildMethod === 'FRAMEWORK' && } +
; } function EmptySourceState({ readonly, onConnect }: { readonly: boolean; onConnect: () => void }) { diff --git a/src/app/project/app/[appId]/general/framework-configuration-card.tsx b/src/app/project/[projectId]/apps/details/source/framework-configuration-card.tsx similarity index 98% rename from src/app/project/app/[appId]/general/framework-configuration-card.tsx rename to src/app/project/[projectId]/apps/details/source/framework-configuration-card.tsx index 552dd3c2..fee08167 100644 --- a/src/app/project/app/[appId]/general/framework-configuration-card.tsx +++ b/src/app/project/[projectId]/apps/details/source/framework-configuration-card.tsx @@ -14,7 +14,7 @@ import { useActionState, useEffect } from 'react'; import { Control, useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; -import { saveFrameworkConfiguration } from './actions'; +import { saveFrameworkConfiguration } from '../configuration/actions'; import { JsFramework, jsFrameworkPresets } from '@/shared/model/js-framework.model'; export default function FrameworkConfigurationCard({ app, readonly }: { app: AppExtendedModel; readonly: boolean }) { diff --git a/src/app/project/app/[appId]/volumes/actions.ts b/src/app/project/[projectId]/apps/details/volumes/actions.ts similarity index 100% rename from src/app/project/app/[appId]/volumes/actions.ts rename to src/app/project/[projectId]/apps/details/volumes/actions.ts diff --git a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-backup-list.tsx b/src/app/project/[projectId]/apps/details/volumes/backups/app-details-backup-list.tsx similarity index 98% rename from src/app/project/[projectId]/app-components/app-drawer-components/app-details-backup-list.tsx rename to src/app/project/[projectId]/apps/details/volumes/backups/app-details-backup-list.tsx index 509bb987..8b30824f 100644 --- a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-backup-list.tsx +++ b/src/app/project/[projectId]/apps/details/volumes/backups/app-details-backup-list.tsx @@ -16,7 +16,7 @@ import type { VolumeBackupExtendedModel } from '@/shared/model/volume-backup-ext import { downloadBackupForVolumeSchedule, getBackupsForVolumeSchedule, -} from '@/app/project/app/[appId]/volumes/actions'; +} from '@/app/project/[projectId]/apps/details/volumes/actions'; export function DrawerBackupList({ volumeBackup, diff --git a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-backups-tab.tsx b/src/app/project/[projectId]/apps/details/volumes/backups/app-details-backups-tab.tsx similarity index 83% rename from src/app/project/[projectId]/app-components/app-drawer-components/app-details-backups-tab.tsx rename to src/app/project/[projectId]/apps/details/volumes/backups/app-details-backups-tab.tsx index 8ab08868..b4b146d5 100644 --- a/src/app/project/[projectId]/app-components/app-drawer-components/app-details-backups-tab.tsx +++ b/src/app/project/[projectId]/apps/details/volumes/backups/app-details-backups-tab.tsx @@ -1,21 +1,22 @@ 'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Archive, Download, Loader2, Pen, Play } from 'lucide-react'; +import { Archive, Database, Download, HardDrive, Loader2, Pen, Play } from 'lucide-react'; import type { S3Target } from '@prisma/client'; import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel } from '@/components/ui/dropdown-menu'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import LoadingSpinner from '@/components/ui/loading-spinner'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Button } from '@/components/ui/button'; import { SplitButton } from '@/components/custom/split-button'; -import VolumeBackupList from '@/app/project/app/[appId]/volumes/volume-backup'; +import VolumeBackupList from '@/app/project/[projectId]/apps/details/volumes/backups/volume-backup'; import { downloadBackupForVolumeSchedule, getBackupsForVolumeSchedule, runBackupVolumeSchedule, -} from '@/app/project/app/[appId]/volumes/actions'; +} from '@/app/project/[projectId]/apps/details/volumes/actions'; import { formatDateTime } from '@/frontend/utils/format.utils'; import { Actions } from '@/frontend/utils/nextjs-actions.utils'; import { Toast } from '@/frontend/utils/toast.utils'; @@ -25,7 +26,7 @@ import type { AppExtendedModel } from '@/shared/model/app-extended.model'; import type { BackupEntry } from '@/shared/model/backup-info.model'; import { RolePermissionEnum } from '@/shared/model/role-extended.model'; import type { VolumeBackupExtendedModel } from '@/shared/model/volume-backup-extended.model'; -import { useNestedDrawer } from './nested-drawer'; +import { useNestedDrawer } from '../../../../../workload-components/drawer/nested-drawer'; type BackupRow = BackupEntry & { schedule: VolumeBackupExtendedModel }; @@ -76,7 +77,6 @@ export function DrawerBackupsTab({ app, role, s3Targets, volumeBackups }: { readonly={readonly} s3Targets={s3Targets} volumeBackups={volumeBackups} - hideCard /> ), }); @@ -159,21 +159,31 @@ export function DrawerBackupsTab({ app, role, s3Targets, volumeBackups }: { No backups yet None of this app's backup schedules has created a backup yet. - : + :
- Created - Size + Created + Size Backup Location - Type - Actions + Type + Actions {backups.map((backup) => { const backupId = `${backup.schedule.id}:${backup.key}`; return {formatDateTime(backup.backupDate, true)} {backup.sizeBytes ? KubeSizeConverter.convertBytesToReadableSize(backup.sizeBytes) : 'Unknown'} - {backup.schedule.target.name} - {backupType(app, backup.schedule)} +
{backup.schedule.target.name}
+ + + }> + {app.appType !== 'APP' && backup.schedule.useDatabaseBackup + ? + {backupType(app, backup.schedule)} + +
+
+ + {volumeBackups.length > 0 && +
{volumeBackups.length} Backup Rules - Cron - Retention + Cron + Retention Backup Method Backup Location - + RBD} /> @@ -99,15 +91,15 @@ export default function VolumeBackupList({ {volumeBackups.map(volumeBackup => ( - {volumeBackup.cron} + {volumeBackup.cron} {volumeBackup.retention} {app.appType !== 'APP' && volumeBackup.useDatabaseBackup ? `Database (${app.appType.toLocaleLowerCase()})` : 'Archive of Volume'} - {volumeBackup.target.name} - + {volumeBackup.target.name} + {volumeBackup.backupBeforeDeployment ? (volumeBackup.failSilently ? 'Yes (silent)' : 'Yes') : 'No'} @@ -157,7 +149,7 @@ export default function VolumeBackupList({
} - {!readonly && + {!readonly && } - +
; } diff --git a/src/app/project/app/[appId]/volumes/shared-storage-edit-overlay.tsx b/src/app/project/[projectId]/apps/details/volumes/shared-storage-edit-overlay.tsx similarity index 100% rename from src/app/project/app/[appId]/volumes/shared-storage-edit-overlay.tsx rename to src/app/project/[projectId]/apps/details/volumes/shared-storage-edit-overlay.tsx diff --git a/src/app/project/app/[appId]/volumes/storage-edit-overlay.tsx b/src/app/project/[projectId]/apps/details/volumes/storage-edit-overlay.tsx similarity index 100% rename from src/app/project/app/[appId]/volumes/storage-edit-overlay.tsx rename to src/app/project/[projectId]/apps/details/volumes/storage-edit-overlay.tsx diff --git a/src/app/project/app/[appId]/volumes/storages.tsx b/src/app/project/[projectId]/apps/details/volumes/volumes-list.tsx similarity index 97% rename from src/app/project/app/[appId]/volumes/storages.tsx rename to src/app/project/[projectId]/apps/details/volumes/volumes-list.tsx index 3ea0698d..8772f630 100644 --- a/src/app/project/app/[appId]/volumes/storages.tsx +++ b/src/app/project/[projectId]/apps/details/volumes/volumes-list.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { AppExtendedModel } from "@/shared/model/app-extended.model"; import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; @@ -30,11 +30,10 @@ type AppVolumeWithCapacity = (AppVolume & { usedPercentage?: number; }); -export default function StorageList({ app, readonly, storageClasses, hideCard = false }: { +export default function StorageList({ app, readonly, storageClasses }: { app: AppExtendedModel; storageClasses: string[]; readonly: boolean; - hideCard?: boolean; }) { const [volumesWithStorage, setVolumesWithStorage] = React.useState(app.appVolumes as AppVolumeWithCapacity[]); @@ -145,15 +144,14 @@ export default function StorageList({ app, readonly, storageClasses, hideCard = } } - const CardWrapper = hideCard ? 'div' : Card; return <> - +
Volumes Add one or more volumes to to configure persistent storage within your container. - {volumesWithStorage.length > 0 && + {volumesWithStorage.length > 0 && {app.appVolumes.length} Storage @@ -310,7 +308,7 @@ export default function StorageList({ app, readonly, storageClasses, hideCard =
} - {!readonly && + {!readonly && } - +
; } diff --git a/src/app/project/[projectId]/choose-template-dialog.tsx b/src/app/project/[projectId]/choose-template-dialog.tsx index a3249e1f..c736f2ff 100644 --- a/src/app/project/[projectId]/choose-template-dialog.tsx +++ b/src/app/project/[projectId]/choose-template-dialog.tsx @@ -4,8 +4,8 @@ import { DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/di import { useEffect, useState } from "react"; import { AppTemplateModel } from "@/shared/model/app-template.model" import { appTemplates, databaseTemplates } from "@/shared/templates/all.templates" -import CreateTemplateAppSetupDialog from "./app-components/create-template-app-setup-dialog" -import CreateTemplateAgentSetupDialog from "./agent-components/create-template-agent-setup-dialog" +import CreateTemplateAppSetupDialog from "./apps/create/create-template-app-setup-dialog" +import CreateTemplateAgentSetupDialog from "./agents/create/create-template-agent-setup-dialog" import { ScrollArea } from "@/components/ui/scroll-area"; import { Input } from "@/components/ui/input"; import { ArrowUpRight, Blocks, Bot, Database, Search } from "lucide-react"; diff --git a/src/app/project/[projectId]/create-project-actions.tsx b/src/app/project/[projectId]/create-project-actions.tsx index 00c06691..20647aed 100644 --- a/src/app/project/[projectId]/create-project-actions.tsx +++ b/src/app/project/[projectId]/create-project-actions.tsx @@ -2,7 +2,7 @@ import { Button } from "@/components/ui/button"; -import { EditAppDialog } from "./app-components/edit-app-dialog"; +import { EditAppDialog } from "./apps/create/edit-app-dialog"; import { Blocks, Bot, Database, File, Plus } from "lucide-react"; import ChooseTemplateDialog from "./choose-template-dialog"; import { @@ -14,7 +14,7 @@ import { DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { CreateAgentDialog } from "./agent-components/create-agent-dialog"; +import { CreateAgentDialog } from "./agents/create/create-agent-dialog"; import { useDialog } from "@/frontend/states/zustand.states"; diff --git a/src/app/project/[projectId]/overview/network-graph/connection-edge.tsx b/src/app/project/[projectId]/overview/network-graph/connection-edge.tsx new file mode 100644 index 00000000..4421f5d9 --- /dev/null +++ b/src/app/project/[projectId]/overview/network-graph/connection-edge.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { SmoothStepEdge, type EdgeProps } from '@xyflow/react'; +import { ProjectNetworkGraphConnectionContextMenu } from './context-menus/project-network-graph-connection-context-menu'; + +type ConnectionEdgeData = { + onDelete?: () => void; + internalHostnames: { hostname: string; port: number }[]; +}; + +export function ConnectionEdge(props: EdgeProps) { + const data = props.data as ConnectionEdgeData | undefined; + if (!data) return ; + return ( + + + + ); +} + diff --git a/src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-agent-context-menu.tsx b/src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-agent-context-menu.tsx similarity index 97% rename from src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-agent-context-menu.tsx rename to src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-agent-context-menu.tsx index 345ac8ff..8621a278 100644 --- a/src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-agent-context-menu.tsx +++ b/src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-agent-context-menu.tsx @@ -11,7 +11,7 @@ import { ContextMenuTrigger, } from '@/components/ui/context-menu'; import { Toast } from '@/frontend/utils/toast.utils'; -import { saveAgentNetworkPolicySettings } from '@/app/project/[projectId]/agent-components/agent-drawer-components/configuration/actions'; +import { saveAgentNetworkPolicySettings } from '@/app/project/[projectId]/agents/details/configuration/actions'; import type { AgentExtendedModel } from '@/shared/model/agent-extended.model'; export type ProjectNetworkGraphAgentContextMenuProps = { diff --git a/src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-app-context-menu.tsx b/src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-app-context-menu.tsx similarity index 98% rename from src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-app-context-menu.tsx rename to src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-app-context-menu.tsx index c04f8f00..a0dbc2e3 100644 --- a/src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-app-context-menu.tsx +++ b/src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-app-context-menu.tsx @@ -12,7 +12,7 @@ import { ContextMenuSubTrigger, ContextMenuTrigger, } from '@/components/ui/context-menu'; -import { deploy, startApp, stopApp } from '@/app/project/app/[appId]/actions'; +import { deploy, startApp, stopApp } from '@/app/project/[projectId]/apps/details/actions'; import { usePodsStatus } from '@/frontend/states/zustand.states'; import { AppLifecycleUtils } from '@/frontend/utils/app-lifecycle.utils'; import { Toast } from '@/frontend/utils/toast.utils'; diff --git a/src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-canvas-context-menu.tsx b/src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-canvas-context-menu.tsx similarity index 96% rename from src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-canvas-context-menu.tsx rename to src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-canvas-context-menu.tsx index 87958896..58b0297d 100644 --- a/src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-canvas-context-menu.tsx +++ b/src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-canvas-context-menu.tsx @@ -12,8 +12,8 @@ import { ContextMenuTrigger, } from '@/components/ui/context-menu'; import { useDialog } from '@/frontend/states/zustand.states'; -import { EditAppDialog } from '@/app/project/[projectId]/app-components/edit-app-dialog'; -import { CreateAgentDialog } from '@/app/project/[projectId]/agent-components/create-agent-dialog'; +import { EditAppDialog } from '@/app/project/[projectId]/apps/create/edit-app-dialog'; +import { CreateAgentDialog } from '@/app/project/[projectId]/agents/create/create-agent-dialog'; import ChooseTemplateDialog from '@/app/project/[projectId]/choose-template-dialog'; type ProjectNetworkGraphCanvasContextMenuProps = { diff --git a/src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-connection-context-menu.tsx b/src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-connection-context-menu.tsx similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/context-menus/project-network-graph-connection-context-menu.tsx rename to src/app/project/[projectId]/overview/network-graph/context-menus/project-network-graph-connection-context-menu.tsx diff --git a/src/app/project/[projectId]/overview/network-graph/graph-legend.tsx b/src/app/project/[projectId]/overview/network-graph/graph-legend.tsx new file mode 100644 index 00000000..5afa1fb2 --- /dev/null +++ b/src/app/project/[projectId]/overview/network-graph/graph-legend.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { cn } from '@/frontend/utils/utils'; +import { graphLegendItems, NETWORK_GRAPH_COLORS } from './project-network-graph-visual-semantics'; + +export function GraphLegend() { + return ( +
+ {graphLegendItems.map(item => ( + + + {item.label} + + ))} + Arrows show allowed traffic direction +
+ ); +} + diff --git a/src/app/project/[projectId]/app-components/project-network-graph/nodes/project-network-graph-agent-workload-node.tsx b/src/app/project/[projectId]/overview/network-graph/nodes/project-network-graph-agent-workload-node.tsx similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/nodes/project-network-graph-agent-workload-node.tsx rename to src/app/project/[projectId]/overview/network-graph/nodes/project-network-graph-agent-workload-node.tsx diff --git a/src/app/project/[projectId]/app-components/project-network-graph/nodes/project-network-graph-app-workload-node.tsx b/src/app/project/[projectId]/overview/network-graph/nodes/project-network-graph-app-workload-node.tsx similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/nodes/project-network-graph-app-workload-node.tsx rename to src/app/project/[projectId]/overview/network-graph/nodes/project-network-graph-app-workload-node.tsx diff --git a/src/app/project/[projectId]/app-components/project-network-graph/nodes/project-network-graph-internet-node.tsx b/src/app/project/[projectId]/overview/network-graph/nodes/project-network-graph-internet-node.tsx similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/nodes/project-network-graph-internet-node.tsx rename to src/app/project/[projectId]/overview/network-graph/nodes/project-network-graph-internet-node.tsx diff --git a/src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-drawer-session.spec.ts b/src/app/project/[projectId]/overview/network-graph/project-network-graph-drawer-session.spec.ts similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-drawer-session.spec.ts rename to src/app/project/[projectId]/overview/network-graph/project-network-graph-drawer-session.spec.ts diff --git a/src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-drawer-session.ts b/src/app/project/[projectId]/overview/network-graph/project-network-graph-drawer-session.ts similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-drawer-session.ts rename to src/app/project/[projectId]/overview/network-graph/project-network-graph-drawer-session.ts diff --git a/src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-focus.spec.ts b/src/app/project/[projectId]/overview/network-graph/project-network-graph-focus.spec.ts similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-focus.spec.ts rename to src/app/project/[projectId]/overview/network-graph/project-network-graph-focus.spec.ts diff --git a/src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-focus.ts b/src/app/project/[projectId]/overview/network-graph/project-network-graph-focus.ts similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-focus.ts rename to src/app/project/[projectId]/overview/network-graph/project-network-graph-focus.ts diff --git a/src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-projection.ts b/src/app/project/[projectId]/overview/network-graph/project-network-graph-projection.ts similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-projection.ts rename to src/app/project/[projectId]/overview/network-graph/project-network-graph-projection.ts diff --git a/src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-visual-semantics.spec.ts b/src/app/project/[projectId]/overview/network-graph/project-network-graph-visual-semantics.spec.ts similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-visual-semantics.spec.ts rename to src/app/project/[projectId]/overview/network-graph/project-network-graph-visual-semantics.spec.ts diff --git a/src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-visual-semantics.ts b/src/app/project/[projectId]/overview/network-graph/project-network-graph-visual-semantics.ts similarity index 100% rename from src/app/project/[projectId]/app-components/project-network-graph/project-network-graph-visual-semantics.ts rename to src/app/project/[projectId]/overview/network-graph/project-network-graph-visual-semantics.ts diff --git a/src/app/project/[projectId]/app-components/project-network-graph.spec.ts b/src/app/project/[projectId]/overview/network-graph/project-network-graph.spec.ts similarity index 97% rename from src/app/project/[projectId]/app-components/project-network-graph.spec.ts rename to src/app/project/[projectId]/overview/network-graph/project-network-graph.spec.ts index c5d29f43..312eb7c0 100644 --- a/src/app/project/[projectId]/app-components/project-network-graph.spec.ts +++ b/src/app/project/[projectId]/overview/network-graph/project-network-graph.spec.ts @@ -1,7 +1,7 @@ import type { AppExtendedModel } from '@/shared/model/app-extended.model'; -import { buildProjectNetworkGraph, connectionDeletionProvenance } from './project-network-graph/project-network-graph-projection'; -import { graphEdgePresentation } from './project-network-graph/project-network-graph-visual-semantics'; +import { buildProjectNetworkGraph, connectionDeletionProvenance } from './project-network-graph-projection'; +import { graphEdgePresentation } from './project-network-graph-visual-semantics'; function app(overrides: Record = {}): AppExtendedModel { return { diff --git a/src/app/project/[projectId]/app-components/project-network-graph.tsx b/src/app/project/[projectId]/overview/network-graph/project-network-graph.tsx similarity index 90% rename from src/app/project/[projectId]/app-components/project-network-graph.tsx rename to src/app/project/[projectId]/overview/network-graph/project-network-graph.tsx index 491f831f..46b47088 100644 --- a/src/app/project/[projectId]/app-components/project-network-graph.tsx +++ b/src/app/project/[projectId]/overview/network-graph/project-network-graph.tsx @@ -1,6 +1,8 @@ 'use client'; import type { CSSProperties } from 'react'; +import { ConnectionEdge } from './connection-edge'; +import { GraphLegend } from './graph-legend'; import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { @@ -9,11 +11,9 @@ import { Controls, MarkerType, ReactFlow, - SmoothStepEdge, useNodesState, type Node, type NodeTypes, - type EdgeProps, type Connection, type ReactFlowInstance, } from '@xyflow/react'; @@ -22,25 +22,23 @@ import { Cloud, Info, RotateCcw } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardFooter } from '@/components/ui/card'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { cn } from '@/frontend/utils/utils'; import type { AppExtendedModel } from '@/shared/model/app-extended.model'; import type { AgentExtendedModel } from '@/shared/model/agent-extended.model'; import type { UserSession } from '@/shared/model/sim-session.model'; import { UserGroupUtils } from '@/shared/utils/role.utils'; import { RolePermissionEnum } from '@/shared/model/role-extended.model'; -import { ProjectNetworkGraphConnectionContextMenu } from './project-network-graph/context-menus/project-network-graph-connection-context-menu'; -import { connectionDeletionProvenance, type NetworkGraphEdge, NetworkGraphNode } from './project-network-graph/project-network-graph-projection'; -import { useProjectNetworkGraph } from './project-network-graph/use-project-network-graph'; -import { graphEdgePresentation, graphLegendItems, NETWORK_GRAPH_COLORS } from './project-network-graph/project-network-graph-visual-semantics'; -import { getFocusedNodeViewport } from './project-network-graph/project-network-graph-focus'; +import { connectionDeletionProvenance, type NetworkGraphEdge, NetworkGraphNode } from './project-network-graph-projection'; +import { useProjectNetworkGraph } from './use-project-network-graph'; +import { graphEdgePresentation } from './project-network-graph-visual-semantics'; +import { getFocusedNodeViewport } from './project-network-graph-focus'; import { useConfirmDialog, useDialog } from '@/frontend/states/zustand.states'; import { Toast } from '@/frontend/utils/toast.utils'; import { AppNetworkPolicyRuleEditModel, NetworkPolicySelectableTarget } from '@/shared/model/app-network-policy-edit.model'; import { NetworkPolicyRuleUtils } from '@/shared/utils/network-policy-rule.utils'; import { AppNetworkPolicyDraft, AppNetworkPolicyDraftUtils } from '@/shared/utils/app-network-policy-draft.utils'; import { InternalHostnameUtils } from '@/shared/utils/internal-hostname.utils'; -import AppNetworkPolicyRuleDialog from '@/app/project/app/[appId]/advanced/app-network-policy-rule-dialog'; -import { saveAppNetworkPolicyConfiguration } from '@/app/project/app/[appId]/advanced/actions'; +import AppNetworkPolicyRuleDialog from '@/app/project/[projectId]/apps/details/network/policy/app-network-policy-rule-dialog'; +import { saveAppNetworkPolicyConfiguration } from '@/app/project/[projectId]/apps/details/network/policy/actions'; import { deleteApp } from '@/app/project/[projectId]/actions'; import type { ProjectNetworkGraphPositions } from '@/shared/model/project-network-graph-layout.model'; import type { S3Target } from '@prisma/client'; @@ -48,21 +46,16 @@ import type { VolumeBackupExtendedModel } from '@/shared/model/volume-backup-ext import { type DrawerTab, useProjectNetworkGraphDrawerSession, -} from './project-network-graph/project-network-graph-drawer-session'; -import { AppDetailsDrawer } from './app-drawer-components/app-details-drawer'; -import { AgentSandboxDrawer } from '@/app/project/[projectId]/agent-components/agent-drawer-components/agent-sandbox-drawer'; -import { deleteAgent } from '@/app/project/[projectId]/agent-components/agent-drawer-components/agent-actions'; +} from './project-network-graph-drawer-session'; +import { AppDetailsDrawer } from '../../apps/details/app-details-drawer'; +import { AgentSandboxDrawer } from '@/app/project/[projectId]/agents/details/agent-drawer'; +import { deleteAgent } from '@/app/project/[projectId]/agents/details/agent-actions'; import type { AgentSandboxTemplateInfo } from '@/shared/model/agent-sandbox-template-info.model'; import type { AgentDrawerTab } from '@/shared/utils/agent-drawer-navigation.utils'; -import { ProjectNetworkGraphCanvasContextMenu } from './project-network-graph/context-menus/project-network-graph-canvas-context-menu'; -import { ProjectNetworkGraphAppWorkloadNode } from './project-network-graph/nodes/project-network-graph-app-workload-node'; -import { ProjectNetworkGraphAgentWorkloadNode } from './project-network-graph/nodes/project-network-graph-agent-workload-node'; -import { ProjectNetworkGraphInternetNode } from './project-network-graph/nodes/project-network-graph-internet-node'; - -type ConnectionEdgeData = { - onDelete?: () => void; - internalHostnames: { hostname: string; port: number }[]; -}; +import { ProjectNetworkGraphCanvasContextMenu } from './context-menus/project-network-graph-canvas-context-menu'; +import { ProjectNetworkGraphAppWorkloadNode } from './nodes/project-network-graph-app-workload-node'; +import { ProjectNetworkGraphAgentWorkloadNode } from './nodes/project-network-graph-agent-workload-node'; +import { ProjectNetworkGraphInternetNode } from './nodes/project-network-graph-internet-node'; type ProjectNetworkGraphProps = { apps: AppExtendedModel[]; @@ -91,19 +84,6 @@ const nodeTypes = { internet: ProjectNetworkGraphInternetNode, } satisfies NodeTypes; -function ConnectionEdge(props: EdgeProps) { - const data = props.data as ConnectionEdgeData | undefined; - if (!data) return ; - return ( - - - - ); -} - function getInternalHostnames( edge: NetworkGraphEdge, drafts: Record, @@ -130,26 +110,6 @@ function getInternalHostnames( } const edgeTypes = { connection: ConnectionEdge }; -function Legend() { - return ( -
- {graphLegendItems.map(item => ( - - - {item.label} - - ))} - Arrows show allowed traffic direction -
- ); -} - export default function ProjectNetworkGraph(props: ProjectNetworkGraphProps) { return } /> - + {canEditLayout && - - ))} - - - - - - - ( - - - TCP Port - - - field.onChange(e.target.value)} /> - - - - )} - /> - - - -
- ( - - - Check Interval (periodSeconds) - - - field.onChange(e.target.value)} /> - - - - )} - /> - ( - - -

Number of seconds to wait for a connection to complete before timing out. Minimum value is 1.

-
- }> - Check Timeout (timeoutSeconds) - - - field.onChange(e.target.value)} /> - - - - )} - /> - ( - - - Failure Threshold - - - field.onChange(e.target.value)} /> - - - - )} - /> - - - )} -
- - Save - - - -
- ); -} diff --git a/src/app/project/app/[appId]/app-action-buttons.tsx b/src/app/project/app/[appId]/app-action-buttons.tsx deleted file mode 100644 index 5f79613e..00000000 --- a/src/app/project/app/[appId]/app-action-buttons.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client' - -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { deploy, startApp, stopApp } from "./actions"; -import { AppExtendedModel } from "@/shared/model/app-extended.model"; -import { Toast } from "@/frontend/utils/toast.utils"; -import { ExternalLink, Hammer, Play, Rocket, Square } from "lucide-react"; -import { AppEventsDialog } from "./app-events-dialog"; -import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; -import { UserSession } from "@/shared/model/sim-session.model"; -import { UserGroupUtils } from "@/shared/utils/role.utils"; -import PodStatusIndicator from "@/components/custom/pod-status-indicator"; -import { usePodsStatus } from "@/frontend/states/zustand.states"; -import { AppSourceUtils } from "@/frontend/utils/app-source.utils"; - -export default function AppActionButtons({ - app, - session -}: { - app: AppExtendedModel; - session: UserSession; -}) { - const hasWriteAccess = UserGroupUtils.sessionHasWriteAccessForApp(session, app.id); - const appSourceIsConfigured = AppSourceUtils.isConfiguredSource(app); - const deploymentStatus = usePodsStatus(state => state.podsStatus.get(app.id)?.deploymentStatus ?? 'UNKNOWN'); - - return - - -
-
- {hasWriteAccess && <> - {app.appType === 'APP' && (app.sourceType === 'GIT' || app.sourceType === 'GIT_SSH') && } - - - } - {app.appDomains.length > 0 && } -
- -
-
-
; -} diff --git a/src/app/project/app/[appId]/app-breadcrumbs.tsx b/src/app/project/app/[appId]/app-breadcrumbs.tsx deleted file mode 100644 index a90b920f..00000000 --- a/src/app/project/app/[appId]/app-breadcrumbs.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { AppExtendedModel } from "@/shared/model/app-extended.model"; -import WorkloadBreadcrumbs from "@/components/custom/workload-breadcrumbs"; - -export default function AppBreadcrumbs({ app, apps, tabName }: { app: AppExtendedModel; apps: { id: string; name: string }[]; tabName?: string }) { - return ( - - ); -} diff --git a/src/app/project/app/[appId]/app-events-dialog.tsx b/src/app/project/app/[appId]/app-events-dialog.tsx deleted file mode 100644 index 34eab994..00000000 --- a/src/app/project/app/[appId]/app-events-dialog.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog" -import React, { useCallback, useEffect } from "react"; -import { AppExtendedModel } from "@/shared/model/app-extended.model"; -import { getLatestAppEvents } from "./actions"; -import { toast } from "sonner"; -import FullLoadingSpinner from "@/components/ui/full-loading-spinnter"; -import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { EventInfoModel } from "@/shared/model/event-info.model"; -import { formatDateTime } from "@/frontend/utils/format.utils"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { cn } from "@/frontend/utils/utils"; - -export function AppEventsDialog({ - app, - children -}: { - app: AppExtendedModel; - children: React.ReactNode; -}) { - - const [isOpen, setIsOpen] = React.useState(false); - const [events, setEvents] = React.useState(undefined); - - const loadEvents = useCallback(async () => { - try { - const eventsResponse = await getLatestAppEvents(app.id); - if (eventsResponse.status === 'success') { - setEvents(eventsResponse.data!); - } else { - toast.error(eventsResponse.message); - } - } catch (error) { - console.error(error); - toast.error('An error occured while loading events.'); - } - }, [app.id]) - - useEffect(() => { - if (isOpen) { - loadEvents(); - } else { - setEvents(undefined); - } - }, [isOpen, loadEvents]); - - return (<> -
setIsOpen(true)} className="cursor-pointer"> {children}
- { - setIsOpen(isO); - }}> - - - App Events - - App events occur when changes are made to the deployment. For example, when a deployment is created, updated, or restarted. - Advanced users can read these events to understand what is happening in the background. Events are only available for a short period of time. - - -
- {!events && } - {events && <> - - - {events.length} recent Events - - - Time - Type - Action - Note - Pod Name - - - - {events.map((event, index) => ( - - {formatDateTime(event.eventTime, true)} - {event.action} - - {event.reason} - - {event.note} - {event.podName} - - ))} - - -
- } -
-
-
- ) -} diff --git a/src/app/project/app/[appId]/app-tabs.tsx b/src/app/project/app/[appId]/app-tabs.tsx deleted file mode 100644 index 7f05cfe9..00000000 --- a/src/app/project/app/[appId]/app-tabs.tsx +++ /dev/null @@ -1,119 +0,0 @@ -'use client' - -import { useEffect, useState } from "react"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import GeneralAppRateLimits from "./general/app-rate-limits"; -import GeneralAppSource from "./general/app-source"; -import GeneralAppContainerConfig from "./general/app-container-config"; -import EnvEdit from "./environment/env-edit"; -import { S3Target } from "@prisma/client"; -import DomainsCard from "../../../../components/custom/domains-card"; -import StorageList from "./volumes/storages"; -import { AppExtendedModel } from "@/shared/model/app-extended.model"; -import BuildsTab from "./overview/deployments"; -import Logs from "./overview/logs"; -import MonitoringTab from "./overview/monitoring-app"; -import NodePortsCard from "./domains/node-ports"; -import FileMountsCard from "@/components/custom/file-mounts-card"; -import WebhookDeploymentInfo from "./overview/webhook-deployment"; -import DbCredentials from "./credentials/db-crendentials"; -import VolumeBackupList from "./volumes/volume-backup"; -import { VolumeBackupExtendedModel } from "@/shared/model/volume-backup-extended.model"; -import BasicAuth from "./advanced/basic-auth"; -import NetworkPolicy from "./advanced/network-policy"; -import HealthCheckSettings from "./advanced/health-check-settings"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import DbToolsCard from "./credentials/db-tools"; -import { RolePermissionEnum } from "@/shared/model/role-extended.model"; -import { Eye, Key, Settings, Zap, Globe, HardDrive, Cog } from "lucide-react"; -import { AppSourceUtils } from "@/frontend/utils/app-source.utils"; -import { TabNavigationUtils } from "@/frontend/utils/tab-navigation.utils"; -import { saveHealthCheck } from "./advanced/actions"; - -export default function AppTabs({ - app, - role, - tabName, - s3Targets, - volumeBackups, - storageClasses, - gitSshPublicKey, -}: { - app: AppExtendedModel; - role: RolePermissionEnum; - tabName: string; - s3Targets: S3Target[]; - volumeBackups: VolumeBackupExtendedModel[]; - storageClasses: string[]; - gitSshPublicKey?: string; -}) { - const readonly = role !== RolePermissionEnum.READWRITE; - const appSourceIsConfigured = AppSourceUtils.isConfiguredSource(app); - const [activeTab, setActiveTab] = useState(tabName); - const openTab = (newTab: string) => { - setActiveTab(newTab); - TabNavigationUtils.replaceQuery(new URLSearchParams({ tabName: newTab }), `/project/app/${app.id}`); - } - - useEffect(() => { - setActiveTab(tabName); - }, [tabName]); - - if (!appSourceIsConfigured) { - return ( - - ) - } - - return ( - openTab(newTab)} className="space-y-4"> - - - Overview - {app.appType !== 'APP' && Credentials} - General - Environment - Domains & Networking - Storage - Advanced - - - - - - - - - {app.appType !== 'APP' && - {role === RolePermissionEnum.READWRITE && } - - } - - - - - - - - - - - - - - - - - - - - - - - - ) -} diff --git a/src/app/project/app/[appId]/general/app-container-config.tsx b/src/app/project/app/[appId]/general/app-container-config.tsx deleted file mode 100644 index 6a8deb49..00000000 --- a/src/app/project/app/[appId]/general/app-container-config.tsx +++ /dev/null @@ -1,225 +0,0 @@ -'use client'; - -import { SubmitButton } from "@/components/custom/submit-button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; -import { FormUtils } from "@/frontend/utils/form.utilts"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { saveGeneralAppContainerConfig } from "./actions"; -import { ServerActionResult } from "@/shared/model/server-action-error-return.model"; -import { Input } from "@/components/ui/input"; -import { Separator } from "@/components/ui/separator"; -import { Switch } from "@/components/ui/switch"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { useActionState, useEffect } from "react"; -import { toast } from "sonner"; -import { AppExtendedModel } from "@/shared/model/app-extended.model"; -import { z } from "zod"; -import { appContainerConfigZodModel } from "@/shared/model/app-container-config.model"; -import ContainerCommandArgsFields, { LabelWithHint } from "@/components/custom/container-command-args-fields"; -import { ContainerCommangArgsUtils } from "@/shared/utils/container-command-args.utils"; - -export type AppContainerConfigInputModel = z.infer; - -export default function GeneralAppContainerConfig({ app, readonly, hideCard = false }: { - app: AppExtendedModel; - readonly: boolean; - hideCard?: boolean; -}) { - const inputValue = (value: unknown) => typeof value === 'string' || typeof value === 'number' ? value : ''; - // Parse containerArgs from JSON string to array - const form = useForm, unknown, z.output>({ - resolver: zodResolver(appContainerConfigZodModel), - defaultValues: { - containerCommand: ContainerCommangArgsUtils.parseStoredContainerCommandItems(app.containerCommand), - containerArgs: app.containerArgs - ? JSON.parse(app.containerArgs).map((arg: string) => ({ value: arg })) - : [], - securityContextRunAsUser: app.securityContextRunAsUser ?? undefined, - securityContextRunAsGroup: app.securityContextRunAsGroup ?? undefined, - securityContextFsGroup: app.securityContextFsGroup ?? undefined, - securityContextPrivileged: app.securityContextPrivileged ?? false, - }, - disabled: readonly, - }); - - const [state, formAction] = useActionState( - (state: ServerActionResult, payload: AppContainerConfigInputModel) => - saveGeneralAppContainerConfig(state, payload, app.id), - FormUtils.getInitialFormState() - ); - - useEffect(() => { - if (state.status === 'success') { - toast.success('Container Configuration Saved', { - description: "Click \"deploy\" to apply the changes to your app.", - }); - } - FormUtils.mapValidationErrorsToForm(state, form) - }, [form, state]); - - const values = form.watch(); - const CardWrapper = hideCard ? 'div' : Card; - - return ( - - {!hideCard && - Container Configuration - - Override image defaults only when your workload needs custom startup behavior or Linux security settings. - - } -
- - form.handleSubmit((data) => { - return formAction(data); - })()}> - -
-
-

Runtime

-

- Leave these fields empty to keep the command and arguments from the container image. -

-
- - -
- - - -
-
-

Security Context

-

- Change these values only when your image, mounted volumes, or tooling require specific Linux permissions. -

-
- -
- ( - - - Run As User - - - field.onChange(e.target.value === '' ? null : Number(e.target.value))} - /> - - - - )} - /> - ( - - - Run As Group - - - field.onChange(e.target.value === '' ? null : Number(e.target.value))} - /> - - - - )} - /> - ( - - - FS Group - - - field.onChange(e.target.value === '' ? null : Number(e.target.value))} - /> - - - - )} - /> -
- - ( - -
- - - -
- -

- Removes most container isolation. The container gets all Linux capabilities, - access to host devices, and can interact with the node almost like a root - process on the host. -

-

- If the container is compromised, it can affect the Kubernetes node and - other workloads. Use this only for workloads such as Docker-in-Docker - or low-level system tooling. -

- - )} - > - Privileged Mode -
- - {values.securityContextPrivileged && - - Enable this only if you fully understand the implications and risks. - - } -
- -
- -
- )} - /> -
-
- {!readonly && ( - - Save -

{state?.message}

-
- )} - -
- -
- ); -} diff --git a/src/app/project/app/[appId]/general/app-source-wizard/source-wizard-fields.tsx b/src/app/project/app/[appId]/general/app-source-wizard/source-wizard-fields.tsx deleted file mode 100644 index db742c1e..00000000 --- a/src/app/project/app/[appId]/general/app-source-wizard/source-wizard-fields.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { cn } from "@/frontend/utils/utils"; -import { Eye, EyeOff, Loader2, RefreshCw, type LucideIcon } from "lucide-react"; -import { type ComponentProps } from "react"; - -export function IconInput({ icon: Icon, label, className, ...props }: ComponentProps & { icon: LucideIcon; label: string }) { - return ( -
- -
- - -
-
- ); -} - -export function SecretInput({ icon: Icon, label, value, visible, onVisibleChange, onChange }: { - icon: LucideIcon; - label: string; - value: string; - visible: boolean; - onVisibleChange: (visible: boolean) => void; - onChange: (value: string) => void; -}) { - const EyeIcon = visible ? EyeOff : Eye; - return ( -
- -
- - onChange(event.target.value)} - /> - -
-
- ); -} - -export function BranchLoadingState({ loading, error, onRetry }: { - loading: boolean; - error: string | null; - onRetry: () => Promise; -}) { - if (loading) { - return ( -
- - Loading branches... -
- ); - } - - if (!error) { - return null; - } - - return ( - - Branches could not be loaded - -

{error}

- -
-
- ); -} - -export function SummaryRow({ label, value, mono }: { label: string; value: string; mono?: boolean }) { - return ( -
-

{label}

-

{value || 'Not configured'}

-
- ); -} - -export function SecretSummaryRow({ label, value, visible, onVisibleChange }: { - label: string; - value: string; - visible: boolean; - onVisibleChange: (visible: boolean) => void; -}) { - const EyeIcon = visible ? EyeOff : Eye; - const displayValue = value ? (visible ? value : '************') : 'Not configured'; - return ( -
-

{label}

-
-

{displayValue}

- {!!value && ( - - )} -
-
- ); -} diff --git a/src/app/project/app/[appId]/layout.tsx b/src/app/project/app/[appId]/layout.tsx deleted file mode 100644 index a153e7dc..00000000 --- a/src/app/project/app/[appId]/layout.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { isAuthorizedReadForWorkload } from "@/server/utils/action-wrapper.utils"; -import appService from "@/server/services/app.service"; -import PageTitle from "@/components/custom/page-title"; -import AppActionButtons from "./app-action-buttons"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { AlertTriangle } from "lucide-react"; -import { AppSourceUtils } from "@/frontend/utils/app-source.utils"; -import Link from "next/link"; - -export default async function RootLayout({ - children, - params -}: Readonly<{ - params: Promise<{ appId: string }> - children: React.ReactNode; -}>) { - - const resolvedParams = await params; - const appId = resolvedParams?.appId; - if (!appId) { - return

Could not find app with id {appId}

- } - const session = await isAuthorizedReadForWorkload(appId); - const app = await appService.getExtendedById(appId); - - const hasIngressRule = (app.appNetworkPolicy?.rules ?? []).some(rule => rule.type === 'INGRESS'); - const appSourceIsConfigured = AppSourceUtils.isConfiguredSource(app); - const showIngressWarning = appSourceIsConfigured && app.useNetworkPolicy && app.appDomains.length === 0 && !hasIngressRule; - - return ( -
- - - {showIngressWarning && ( - - - No inbound traffic configured - - This app has no domain and no ingress network policy rule. It accepts no incoming traffic and cannot be reached - from other apps. Configure inbound connections in{" "} - - Domains & Networking - - . - - - )} - - {children} -
- ); -} diff --git a/src/app/project/app/[appId]/overview/build-status-badge.tsx b/src/app/project/app/[appId]/overview/build-status-badge.tsx deleted file mode 100644 index 10930952..00000000 --- a/src/app/project/app/[appId]/overview/build-status-badge.tsx +++ /dev/null @@ -1,67 +0,0 @@ -'use client' - - - -import { BuildJobStatus } from "@/shared/model/build-job"; - - -export default function BuildStatusBadge( - { - children - }: { - children: BuildJobStatus - } -) { - - return (<> - {getTextForStatus(children)} - - ) -} - -function getTextForStatus(status: BuildJobStatus) { - switch (status) { - case 'UNKNOWN': - return 'Unknown'; - case 'FAILED': - return 'Failed'; - case 'RUNNING': - return 'Running'; - case 'SUCCEEDED': - return 'Success'; - default: - return 'Unknown'; - } -} - -function getBackgroundColorForStatus(status: BuildJobStatus) { - switch (status) { - - case 'UNKNOWN': - return 'bg-slate-100'; - case 'FAILED': - return 'bg-red-100'; - case 'RUNNING': - return 'bg-blue-100'; - case 'SUCCEEDED': - return 'bg-green-100'; - default: - return 'bg-slate-100'; - } -} - -function getTextColorForStatus(status: BuildJobStatus) { - switch (status) { - - case 'UNKNOWN': - return 'text-slate-800'; - case 'FAILED': - return 'text-red-800'; - case 'RUNNING': - return 'text-blue-800'; - case 'SUCCEEDED': - return 'text-green-800'; - default: - return 'text-slate-800'; - } -} \ No newline at end of file diff --git a/src/app/project/app/[appId]/overview/deployments-default-view.tsx b/src/app/project/app/[appId]/overview/deployments-default-view.tsx deleted file mode 100644 index ee04023c..00000000 --- a/src/app/project/app/[appId]/overview/deployments-default-view.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { EllipsisVertical, RotateCcw } from 'lucide-react'; -import { SimpleDataTable } from '@/components/custom/simple-data-table'; -import ShortCommitHash from '@/components/custom/short-commit-hash'; -import { Button } from '@/components/ui/button'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { formatDateTime } from '@/frontend/utils/format.utils'; -import type { DeploymentInfoModel } from '@/shared/model/deployment-info.model'; -import { appBuildMethodLabels } from '@/shared/model/app-source-info.model'; -import DeploymentStatusBadge from './deployment-status-badge'; - -type DeploymentsDefaultViewProps = { - deployments: DeploymentInfoModel[]; - canStopBuild: (deployment: DeploymentInfoModel) => boolean; - canRollback: (deployment: DeploymentInfoModel) => boolean; - onShowLogs: (deployment: DeploymentInfoModel) => void; - onStopBuild: (deployment: DeploymentInfoModel) => void; - onRollback: (deployment: DeploymentInfoModel) => void; -}; - -export function DeploymentsDefaultView({ - deployments, - canStopBuild, - canRollback, - onShowLogs, - onStopBuild, - onRollback, -}: DeploymentsDefaultViewProps) { - return ( - ( -
- {deployment.status} - {deployment.isRollback && ( - - Rollback - - )} -
- )], - ['buildMethod', 'Build Method', true, deployment => ( - - {deployment.buildMethod - ? appBuildMethodLabels[deployment.buildMethod] ?? '—' - : '—'} - - )], - ['startTime', 'Started At', true, deployment => formatDateTime(deployment.createdAt)], - ['gitCommit', 'Git Commit', true, deployment => ( - {deployment.gitCommit} - )], - ['gitCommitMessage', 'Commit Message', true, deployment => ( - - {deployment.gitCommitMessage ?? ''} - - )], - ]} - data={deployments} - hideSearchBar - actionCol={deployment => ( -
- - {canStopBuild(deployment) && ( - - )} - {canRollback(deployment) && ( - - - - Deployment actions - } /> - - onRollback(deployment)}> - - Rollback to this deployment - - - - )} -
- )} - /> - ); -} diff --git a/src/app/project/app/[appId]/page.tsx b/src/app/project/app/[appId]/page.tsx deleted file mode 100644 index 39528acc..00000000 --- a/src/app/project/app/[appId]/page.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { isAuthorizedReadForApp } from "@/server/utils/action-wrapper.utils"; -import appService from "@/server/services/app.service"; -import AppTabs from "./app-tabs"; -import AppBreadcrumbs from "./app-breadcrumbs"; -import s3TargetService from "@/server/services/s3-target.service"; -import volumeBackupService from "@/server/services/volume-backup.service"; -import { UserGroupUtils } from "@/shared/utils/role.utils"; -import clusterService from "@/server/services/cluster.service"; -import appGitSshKeyService from "@/server/services/app-git-ssh-key.service"; - -export default async function AppPage({ - searchParams, - params -}: { - searchParams?: Promise<{ [key: string]: string | undefined }>; - params: Promise<{ appId: string }> -}) { - const resolvedParams = await params; - const resolvedSearchParams = await searchParams; - const appId = resolvedParams?.appId; - if (!appId) { - return

Could not find app with id {appId}

- } - const session = await isAuthorizedReadForApp(appId); - const role = UserGroupUtils.getRolePermissionForApp(session, appId); - const app = await appService.getExtendedById(appId); - const [s3Targets, volumeBackups, storageClasses, apps, gitSshPublicKey] = await Promise.all([ - s3TargetService.getAll(), - volumeBackupService.getForApp(appId), - clusterService.getStorageClasses(), - appService.getAllAppsByProjectId(app.projectId), - appGitSshKeyService.getPublicKey(appId), - ]); - - return (<> - - - - ) -} diff --git a/src/app/project/app/[appId]/volumes/storage-restore-overlay.tsx b/src/app/project/app/[appId]/volumes/storage-restore-overlay.tsx deleted file mode 100644 index 963fa568..00000000 --- a/src/app/project/app/[appId]/volumes/storage-restore-overlay.tsx +++ /dev/null @@ -1,97 +0,0 @@ -'use client' - -import type { z } from "zod"; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" - -import { useActionState, useEffect, useState } from "react"; -import { FormUtils } from "@/frontend/utils/form.utilts"; -import { SubmitButton } from "@/components/custom/submit-button"; -import { AppVolume } from "@prisma/client" -import { ServerActionResult } from "@/shared/model/server-action-error-return.model" -import { restoreVolumeFromZip } from "./actions" -import { toast } from "sonner" -import { AppExtendedModel } from "@/shared/model/app-extended.model" -import { volumeUploadZodModel } from "@/shared/model/volume-upload.model" - -export default function StorageRestoreDialog({ children, volume, app }: { children: React.ReactNode; volume: AppVolume; app: AppExtendedModel; }) { - - const [isOpen, setIsOpen] = useState(false); - - - const form = useForm, unknown, z.output>({ - resolver: zodResolver(volumeUploadZodModel) - }); - - const [state, formAction] = useActionState((state: ServerActionResult, payload: FormData) => - restoreVolumeFromZip(state, payload, volume.id), FormUtils.getInitialFormState()); - - useEffect(() => { - if (state.status === 'success') { - form.reset(); - toast.success('Uploaded saved successfully', { - }); - setIsOpen(false); - } - FormUtils.mapValidationErrorsToForm(state, form); - }, [form, state]); - - useEffect(() => { - form.reset(); - }, [volume, app, children, form]); - - return ( - <> -
setIsOpen(true)}> - {children} -
- setIsOpen(false)}> - - - Restore Volume - - Provide a gzip file to restore the volume. - The archive will be extracted at the root of the mount path. - Make sure that the volume has enough space to store the extracted data. - - -
- -
- ( - - Restore File - - - - - - )} - /> - -

{state.message ?? 'All existing data on the volume will be lost!'}

- Restore -
-
- -
-
- - ) - - - -} diff --git a/src/app/project/app/[appId]/overview/build-logs-overlay.tsx b/src/app/project/workload-components/build-logs-dialog-content.tsx similarity index 100% rename from src/app/project/app/[appId]/overview/build-logs-overlay.tsx rename to src/app/project/workload-components/build-logs-dialog-content.tsx diff --git a/src/app/project/app/[appId]/overview/deployment-status-badge.tsx b/src/app/project/workload-components/deployment-status-badge.tsx similarity index 100% rename from src/app/project/app/[appId]/overview/deployment-status-badge.tsx rename to src/app/project/workload-components/deployment-status-badge.tsx diff --git a/src/app/project/workload-components/drawer/drawer-tab-scroll-area.tsx b/src/app/project/workload-components/drawer/drawer-tab-scroll-area.tsx new file mode 100644 index 00000000..19f24c7c --- /dev/null +++ b/src/app/project/workload-components/drawer/drawer-tab-scroll-area.tsx @@ -0,0 +1,16 @@ +'use client'; + +import type { ReactNode } from 'react'; +import { ScrollArea } from '@/components/ui/scroll-area'; + +export function DrawerTabScrollArea({ children }: { children: ReactNode }) { + return ( + +
{children}
+
+ ); +} + diff --git a/src/app/project/[projectId]/app-components/app-drawer-components/nested-drawer.tsx b/src/app/project/workload-components/drawer/nested-drawer.tsx similarity index 84% rename from src/app/project/[projectId]/app-components/app-drawer-components/nested-drawer.tsx rename to src/app/project/workload-components/drawer/nested-drawer.tsx index 8c1b3f99..f9177052 100644 --- a/src/app/project/[projectId]/app-components/app-drawer-components/nested-drawer.tsx +++ b/src/app/project/workload-components/drawer/nested-drawer.tsx @@ -9,10 +9,12 @@ import { type ReactNode, } from 'react'; import { Button } from '@/components/ui/button'; +import { createPortal } from 'react-dom'; import { Drawer, DrawerContent, DrawerDescription, + DrawerFooter, DrawerHeader, DrawerTitle, } from '@/components/ui/drawer'; @@ -27,12 +29,14 @@ type NestedDrawerOptions = { type NestedDrawerContextValue = { openNestedDrawer: (options: NestedDrawerOptions) => void; closeNestedDrawer: () => void; + footerTarget: HTMLDivElement | null; }; const NestedDrawerContext = createContext(null); export function NestedDrawerProvider({ children }: { children: ReactNode }) { const [drawer, setDrawer] = useState(null); + const [footerTarget, setFooterTarget] = useState(null); const closeNestedDrawer = useCallback(() => setDrawer(null), []); const openNestedDrawer = useCallback( (options: NestedDrawerOptions) => setDrawer(options), @@ -41,7 +45,7 @@ export function NestedDrawerProvider({ children }: { children: ReactNode }) { return ( {children} +
); } +export function NestedDrawerFooter({ children }: { children: ReactNode }) { + const { footerTarget } = useNestedDrawer(); + + return footerTarget ? createPortal( + {children}, + footerTarget, + ) : null; +} + export function useNestedDrawer() { const context = useContext(NestedDrawerContext); diff --git a/src/app/project/[projectId]/app-components/app-drawer-components/settings-section.tsx b/src/app/project/workload-components/drawer/settings-section.tsx similarity index 100% rename from src/app/project/[projectId]/app-components/app-drawer-components/settings-section.tsx rename to src/app/project/workload-components/drawer/settings-section.tsx diff --git a/src/app/project/workload-components/health-check-settings.tsx b/src/app/project/workload-components/health-check-settings.tsx new file mode 100644 index 00000000..edce5322 --- /dev/null +++ b/src/app/project/workload-components/health-check-settings.tsx @@ -0,0 +1,332 @@ +'use client' + +import type { z } from "zod"; +import { HealthCheckModel, HealthCheckWorkload, healthCheckZodModel } from "@/shared/model/health-check.model"; +import { useForm, useFieldArray } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Form, FormField, FormItem, FormControl, FormMessage, FormLabel } from "@/components/ui/form"; +import { Switch } from "@/components/ui/switch"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Trash, Plus } from "lucide-react"; +import FormLabelWithQuestion from "@/components/custom/form-label-with-question"; +import { useActionState, useEffect, useId } from "react"; +import { toast } from "sonner"; +import { SubmitButton } from "@/components/custom/submit-button"; +import { FormUtils } from "@/frontend/utils/form.utilts"; +import { ServerActionResult } from "@/shared/model/server-action-error-return.model"; +import { NestedDrawerFooter } from "./drawer/nested-drawer"; + +export default function HealthCheckSettings({ workload, readonly, saveHealthCheck }: { + workload: HealthCheckWorkload; + readonly: boolean; + saveHealthCheck: (state: ServerActionResult, payload: HealthCheckModel) => Promise>; +}) { + const formId = useId(); + + const defaultHeaders = workload.healthCheckHttpHeadersJson + ? JSON.parse(workload.healthCheckHttpHeadersJson) + : []; + + const isEnabled = !!(workload.healthChechHttpGetPath || workload.healthCheckTcpPort); + const probeType = workload.healthChechHttpGetPath ? "HTTP" : workload.healthCheckTcpPort ? "TCP" : "HTTP"; + + const defaultValues: HealthCheckModel = { + workloadId: workload.id, + enabled: isEnabled, + probeType: probeType as "HTTP" | "TCP", + path: workload.healthChechHttpGetPath || undefined, + httpPort: workload.healthCheckHttpPort || undefined, + scheme: (workload.healthCheckHttpScheme as "HTTP" | "HTTPS") || "HTTP", + periodSeconds: workload.healthCheckPeriodSeconds ?? 15, + timeoutSeconds: workload.healthCheckTimeoutSeconds ?? 5, + failureThreshold: workload.healthCheckFailureThreshold ?? 3, + headers: defaultHeaders, + tcpPort: workload.healthCheckTcpPort || undefined, + }; + + const form = useForm, unknown, z.output>({ + resolver: zodResolver(healthCheckZodModel), + defaultValues, + disabled: readonly, + }); + + const { fields, append, remove } = useFieldArray({ + control: form.control, + name: "headers" + }); + + const enabled = form.watch("enabled"); + const probeTypeWatch = form.watch("probeType"); + + const [state, formAction] = useActionState( + saveHealthCheck, + FormUtils.getInitialFormState() + ); + + useEffect(() => { + if (state.status === 'success') { + toast.success('Health Check Settings Saved'); + } + FormUtils.mapValidationErrorsToForm(state, form); + }, [form, state]); + + return ( +
+ form.handleSubmit((data) => { + formAction(data); + })()}> +
+ ( + +
+ Enable Health Check +
+ + + +
+ )} + /> + + {enabled && ( + <> + form.setValue('probeType', value as "HTTP" | "TCP")} className="w-full"> + + HTTP Probe + TCP Probe + + + +
+ ( + + + HTTP Path + + + + + + + )} + /> + ( + + + HTTP Port + + + field.onChange(e.target.value)} /> + + + + )} + /> + ( + + +

Scheme to use for connecting to the container. Defaults to HTTP.

+

Possible enum values:

+
    +
  • "HTTP" means that the scheme used will be http://
  • +
  • "HTTPS" means that the scheme used will be https://
  • +
+
+ }> + HTTP Scheme + + + + + )} + /> +
+ +
+ +

Custom headers to set in the request. HTTP allows repeated headers.

+
+ }> + HTTP Headers + +
+ {fields.map((item, index) => ( +
+ ( + + {index === 0 &&
+ Name + + {''} + +
} + + + + +
+ )} + /> + ( + + {index === 0 &&
+ Value + + {''} + +
} + + + + +
+ )} + /> + +
+ ))} + +
+
+ + + + ( + + + TCP Port + + + field.onChange(e.target.value)} /> + + + + )} + /> + + + +
+ ( + + + Check Interval (periodSeconds) + + + field.onChange(e.target.value)} /> + + + + )} + /> + ( + + +

Number of seconds to wait for a connection to complete before timing out. Minimum value is 1.

+
+ }> + Check Timeout (timeoutSeconds) + + + field.onChange(e.target.value)} /> + + + + )} + /> + ( + + + Failure Threshold + + + field.onChange(e.target.value)} /> + + + + )} + /> + + + )} + + {!readonly && + Save + } + + + ); +} diff --git a/src/app/project/app/[appId]/advanced/health-check.model.ts b/src/app/project/workload-components/health-check.model.ts similarity index 100% rename from src/app/project/app/[appId]/advanced/health-check.model.ts rename to src/app/project/workload-components/health-check.model.ts diff --git a/src/app/project/app/[appId]/general/app-source-wizard/container-image-step.tsx b/src/app/project/workload-components/source-wizard/container-image-step.tsx similarity index 89% rename from src/app/project/app/[appId]/general/app-source-wizard/container-image-step.tsx rename to src/app/project/workload-components/source-wizard/container-image-step.tsx index 2e32acc1..981d74d8 100644 --- a/src/app/project/app/[appId]/general/app-source-wizard/container-image-step.tsx +++ b/src/app/project/workload-components/source-wizard/container-image-step.tsx @@ -1,5 +1,6 @@ import { LockKeyhole, Package, User } from "lucide-react"; -import { IconInput, SecretInput } from "./source-wizard-fields"; +import { IconInput } from '@/app/project/workload-components/source-wizard/fields/icon-input'; +import { SecretInput } from '@/app/project/workload-components/source-wizard/fields/secret-input'; import { SourceFormPatch, SourceWizardInput } from "./types"; export function ContainerImageStep({ formData, showCredentials, showPassword, setShowPassword, onChange }: { diff --git a/src/app/project/app/[appId]/general/app-source-wizard/dockerfile-path-step.tsx b/src/app/project/workload-components/source-wizard/dockerfile-path-step.tsx similarity index 95% rename from src/app/project/app/[appId]/general/app-source-wizard/dockerfile-path-step.tsx rename to src/app/project/workload-components/source-wizard/dockerfile-path-step.tsx index 26d47cfc..aa1034fd 100644 --- a/src/app/project/app/[appId]/general/app-source-wizard/dockerfile-path-step.tsx +++ b/src/app/project/workload-components/source-wizard/dockerfile-path-step.tsx @@ -1,6 +1,6 @@ import { CheckCircle2, FileCode2, Loader2, RefreshCw, SearchX } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { IconInput } from "./source-wizard-fields"; +import { IconInput } from '@/app/project/workload-components/source-wizard/fields/icon-input'; import { defaultDockerfilePath } from "./types"; export function DockerfilePathStep({ value, isDetecting, detectionResult, onChange, onRetry }: { diff --git a/src/app/project/workload-components/source-wizard/fields/branch-loading-state.tsx b/src/app/project/workload-components/source-wizard/fields/branch-loading-state.tsx new file mode 100644 index 00000000..70806770 --- /dev/null +++ b/src/app/project/workload-components/source-wizard/fields/branch-loading-state.tsx @@ -0,0 +1,36 @@ +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Loader2, RefreshCw } from "lucide-react"; + +export function BranchLoadingState({ loading, error, onRetry }: { + loading: boolean; + error: string | null; + onRetry: () => Promise; +}) { + if (loading) { + return ( +
+ + Loading branches... +
+ ); + } + + if (!error) { + return null; + } + + return ( + + Branches could not be loaded + +

{error}

+ +
+
+ ); +} + diff --git a/src/app/project/workload-components/source-wizard/fields/icon-input.tsx b/src/app/project/workload-components/source-wizard/fields/icon-input.tsx new file mode 100644 index 00000000..a6aba94c --- /dev/null +++ b/src/app/project/workload-components/source-wizard/fields/icon-input.tsx @@ -0,0 +1,18 @@ +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/frontend/utils/utils"; +import { type LucideIcon } from "lucide-react"; +import { type ComponentProps } from "react"; + +export function IconInput({ icon: Icon, label, className, ...props }: ComponentProps & { icon: LucideIcon; label: string }) { + return ( +
+ +
+ + +
+
+ ); +} + diff --git a/src/app/project/workload-components/source-wizard/fields/secret-input.tsx b/src/app/project/workload-components/source-wizard/fields/secret-input.tsx new file mode 100644 index 00000000..faaff6a7 --- /dev/null +++ b/src/app/project/workload-components/source-wizard/fields/secret-input.tsx @@ -0,0 +1,40 @@ +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Eye, EyeOff, type LucideIcon } from "lucide-react"; + +export function SecretInput({ icon: Icon, label, value, visible, onVisibleChange, onChange }: { + icon: LucideIcon; + label: string; + value: string; + visible: boolean; + onVisibleChange: (visible: boolean) => void; + onChange: (value: string) => void; +}) { + const EyeIcon = visible ? EyeOff : Eye; + return ( +
+ +
+ + onChange(event.target.value)} + /> + +
+
+ ); +} + diff --git a/src/app/project/workload-components/source-wizard/fields/secret-summary-row.tsx b/src/app/project/workload-components/source-wizard/fields/secret-summary-row.tsx new file mode 100644 index 00000000..2ac784e5 --- /dev/null +++ b/src/app/project/workload-components/source-wizard/fields/secret-summary-row.tsx @@ -0,0 +1,26 @@ +import { Button } from "@/components/ui/button"; +import { Eye, EyeOff } from "lucide-react"; + +export function SecretSummaryRow({ label, value, visible, onVisibleChange }: { + label: string; + value: string; + visible: boolean; + onVisibleChange: (visible: boolean) => void; +}) { + const EyeIcon = visible ? EyeOff : Eye; + const displayValue = value ? (visible ? value : '************') : 'Not configured'; + return ( +
+

{label}

+
+

{displayValue}

+ {!!value && ( + + )} +
+
+ ); +} diff --git a/src/app/project/workload-components/source-wizard/fields/summary-row.tsx b/src/app/project/workload-components/source-wizard/fields/summary-row.tsx new file mode 100644 index 00000000..acc76d26 --- /dev/null +++ b/src/app/project/workload-components/source-wizard/fields/summary-row.tsx @@ -0,0 +1,11 @@ +import { cn } from "@/frontend/utils/utils"; + +export function SummaryRow({ label, value, mono }: { label: string; value: string; mono?: boolean }) { + return ( +
+

{label}

+

{value || 'Not configured'}

+
+ ); +} + diff --git a/src/app/project/app/[appId]/general/app-source-wizard/git-branch-step.tsx b/src/app/project/workload-components/source-wizard/git-branch-step.tsx similarity index 100% rename from src/app/project/app/[appId]/general/app-source-wizard/git-branch-step.tsx rename to src/app/project/workload-components/source-wizard/git-branch-step.tsx diff --git a/src/app/project/app/[appId]/general/app-source-wizard/git-https-url-step.tsx b/src/app/project/workload-components/source-wizard/git-https-url-step.tsx similarity index 85% rename from src/app/project/app/[appId]/general/app-source-wizard/git-https-url-step.tsx rename to src/app/project/workload-components/source-wizard/git-https-url-step.tsx index f57b1df4..cefe7408 100644 --- a/src/app/project/app/[appId]/general/app-source-wizard/git-https-url-step.tsx +++ b/src/app/project/workload-components/source-wizard/git-https-url-step.tsx @@ -1,5 +1,7 @@ import { Link as LinkIcon, LockKeyhole, User } from "lucide-react"; -import { BranchLoadingState, IconInput, SecretInput } from "./source-wizard-fields"; +import { BranchLoadingState } from '@/app/project/workload-components/source-wizard/fields/branch-loading-state'; +import { IconInput } from '@/app/project/workload-components/source-wizard/fields/icon-input'; +import { SecretInput } from '@/app/project/workload-components/source-wizard/fields/secret-input'; import { SourceFormPatch, SourceWizardInput } from "./types"; export function GitHttpsUrlStep({ formData, showCredentials, showToken, setShowToken, onChange, isLoadingBranches, branchError, onRetry }: { diff --git a/src/app/project/app/[appId]/general/app-source-wizard/git-ssh-url-step.tsx b/src/app/project/workload-components/source-wizard/git-ssh-url-step.tsx similarity index 94% rename from src/app/project/app/[appId]/general/app-source-wizard/git-ssh-url-step.tsx rename to src/app/project/workload-components/source-wizard/git-ssh-url-step.tsx index e2be7328..705dd182 100644 --- a/src/app/project/app/[appId]/general/app-source-wizard/git-ssh-url-step.tsx +++ b/src/app/project/workload-components/source-wizard/git-ssh-url-step.tsx @@ -3,7 +3,8 @@ import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Check, ClipboardCopy, KeyRound, Link as LinkIcon, RefreshCw } from "lucide-react"; -import { BranchLoadingState, IconInput } from "./source-wizard-fields"; +import { BranchLoadingState } from '@/app/project/workload-components/source-wizard/fields/branch-loading-state'; +import { IconInput } from '@/app/project/workload-components/source-wizard/fields/icon-input'; import { SourceFormPatch, SourceWizardInput } from "./types"; export function GitSshUrlStep({ formData, publicKey, isEnsuringKey, isLoadingBranches, branchError, onChange, onCopy, onRegenerate, onRetry }: { diff --git a/src/app/project/app/[appId]/general/app-source-wizard/public-deploy-key-dialog.tsx b/src/app/project/workload-components/source-wizard/public-deploy-key-dialog.tsx similarity index 100% rename from src/app/project/app/[appId]/general/app-source-wizard/public-deploy-key-dialog.tsx rename to src/app/project/workload-components/source-wizard/public-deploy-key-dialog.tsx diff --git a/src/app/project/app/[appId]/general/app-source-wizard/readonly-info.tsx b/src/app/project/workload-components/source-wizard/readonly-info.tsx similarity index 100% rename from src/app/project/app/[appId]/general/app-source-wizard/readonly-info.tsx rename to src/app/project/workload-components/source-wizard/readonly-info.tsx diff --git a/src/app/project/app/[appId]/general/app-source-wizard/source-summary-step.tsx b/src/app/project/workload-components/source-wizard/source-summary-step.tsx similarity index 94% rename from src/app/project/app/[appId]/general/app-source-wizard/source-summary-step.tsx rename to src/app/project/workload-components/source-wizard/source-summary-step.tsx index 78e11f6c..6c255e53 100644 --- a/src/app/project/app/[appId]/general/app-source-wizard/source-summary-step.tsx +++ b/src/app/project/workload-components/source-wizard/source-summary-step.tsx @@ -1,6 +1,7 @@ import { AppBuildMethod } from "@/shared/model/app-source-info.model"; import { JsFramework, jsFrameworkPresets } from "@/shared/model/js-framework.model"; -import { SecretSummaryRow, SummaryRow } from "./source-wizard-fields"; +import { SecretSummaryRow } from '@/app/project/workload-components/source-wizard/fields/secret-summary-row'; +import { SummaryRow } from '@/app/project/workload-components/source-wizard/fields/summary-row'; import { buildMethodLabels, defaultDockerfilePath, SourceType, sourceTypeLabels, SourceWizardInput } from "./types"; export function SourceSummaryStep({ formData, publicKey, showGitToken, setShowGitToken, showRegistryPassword, setShowRegistryPassword }: { diff --git a/src/app/project/app/[appId]/general/app-source-wizard/source-type-step.tsx b/src/app/project/workload-components/source-wizard/source-type-step.tsx similarity index 100% rename from src/app/project/app/[appId]/general/app-source-wizard/source-type-step.tsx rename to src/app/project/workload-components/source-wizard/source-type-step.tsx diff --git a/src/app/project/app/[appId]/general/app-source-wizard/types.ts b/src/app/project/workload-components/source-wizard/types.ts similarity index 100% rename from src/app/project/app/[appId]/general/app-source-wizard/types.ts rename to src/app/project/workload-components/source-wizard/types.ts diff --git a/src/app/project/app/[appId]/general/app-source-wizard/wizard-progress.tsx b/src/app/project/workload-components/source-wizard/wizard-progress.tsx similarity index 100% rename from src/app/project/app/[appId]/general/app-source-wizard/wizard-progress.tsx rename to src/app/project/workload-components/source-wizard/wizard-progress.tsx diff --git a/src/app/settings/integrations/llm-gateways/llm-gateway-edit-overlay.tsx b/src/app/settings/integrations/llm-gateways/llm-gateway-edit-overlay.tsx index ef48098a..ac260ae3 100644 --- a/src/app/settings/integrations/llm-gateways/llm-gateway-edit-overlay.tsx +++ b/src/app/settings/integrations/llm-gateways/llm-gateway-edit-overlay.tsx @@ -35,7 +35,7 @@ import { import { Label } from "@/components/ui/label"; import { CloudUpload, FilePenLine } from "lucide-react"; import { ProjectExtendedModel } from "@/shared/model/project-extended.model"; -import { deploy } from "@/app/project/app/[appId]/actions"; +import { deploy } from "@/app/project/[projectId]/apps/details/actions"; function LlmGatewayForm({ gateway }: { gateway?: LlmGatewayModel }) { const { closeDialog } = useDialogContext(); diff --git a/src/app/sidebar-client.tsx b/src/app/sidebar-client.tsx index 6950f7a0..746926ca 100644 --- a/src/app/sidebar-client.tsx +++ b/src/app/sidebar-client.tsx @@ -68,14 +68,7 @@ export function SidebarCient({ const showSettingsNavigation = path.startsWith("/settings") useEffect(() => { - if (path.startsWith('/project/app/')) { - const appId = path.split('/')[3]; - const project = projects.find(p => p.apps.some(a => a.id === appId)); - setCurrentlySelectedProjectId(project?.id || null); - setCurrentlySelectedAppId(appId); - setCurrentlySelectedAgentId(null); - - } else if (path.startsWith("/project")) { + if (path.startsWith("/project")) { const projectId = path.split('/')[2]; setCurrentlySelectedProjectId(projectId); setCurrentlySelectedAppId(searchParams.get('drawerAppId')); diff --git a/src/components/custom/build-status-indicator.tsx b/src/components/custom/build-status-indicator.tsx index 7f42ae13..eac58add 100644 --- a/src/components/custom/build-status-indicator.tsx +++ b/src/components/custom/build-status-indicator.tsx @@ -5,7 +5,7 @@ import { CircleAlert } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { cn } from '@/frontend/utils/utils'; import { useDialog } from '@/frontend/states/zustand.states'; -import { BuildLogsDialogContent } from '@/app/project/app/[appId]/overview/build-logs-overlay'; +import { BuildLogsDialogContent } from '@/app/project/workload-components/build-logs-dialog-content'; import { Tooltip, TooltipContent, diff --git a/src/components/custom/build-status-indicator.unit.spec.ts b/src/components/custom/build-status-indicator.unit.spec.ts index 44737b80..f82f9772 100644 --- a/src/components/custom/build-status-indicator.unit.spec.ts +++ b/src/components/custom/build-status-indicator.unit.spec.ts @@ -12,7 +12,7 @@ vi.mock('@/components/ui/tooltip', () => ({ TooltipContent: ({ children }: { children: React.ReactNode }) => children, })); -vi.mock('@/app/project/app/[appId]/overview/build-logs-overlay', () => ({ +vi.mock('@/app/project/workload-components/build-logs-dialog-content', () => ({ BuildLogsDialogContent: () => null, })); diff --git a/src/components/custom/domains-card.tsx b/src/components/custom/domains-card.tsx index 6f826495..848091d7 100644 --- a/src/components/custom/domains-card.tsx +++ b/src/components/custom/domains-card.tsx @@ -1,9 +1,9 @@ 'use client'; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; -import { CheckIcon, EditIcon, ExternalLink, Plus, TrashIcon, XIcon } from "lucide-react"; +import { EditIcon, ExternalLink, Plus, TrashIcon } from "lucide-react"; import { Toast } from "@/frontend/utils/toast.utils"; import { Code } from "@/components/custom/code"; import { useConfirmDialog, useDialog } from "@/frontend/states/zustand.states"; @@ -13,12 +13,11 @@ import DomainEditOverlay from "@/components/custom/domain-edit-overlay"; import { deleteDomain } from "@/app/project/actions"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -export default function DomainsCard({ domains, workloadId, workloadType, readonly, hideCard = false }: { +export default function DomainsCard({ domains, workloadId, workloadType, readonly }: { domains: DomainEditModel[]; workloadId: string; workloadType: WorkloadType; readonly: boolean; - hideCard?: boolean; }) { const { openConfirmDialog } = useConfirmDialog(); const { openDialog } = useDialog(); @@ -42,26 +41,21 @@ export default function DomainsCard({ domains, workloadId, workloadType, readonl maxWidth: 'max-w-2xl', }); } - const CardWrapper = hideCard ? 'div' : Card; return <> - +
Domains Add custom domains. If a domain is configured, it will be public and accessible via the internet. - {domains.length > 0 && + {domains.length > 0 && {domains.length} Domains Name - {!hideCard && <> - Port - SSL - Redirect HTTP to HTTPS - } + {!readonly && } @@ -74,11 +68,7 @@ export default function DomainsCard({ domains, workloadId, workloadType, readonl - {!hideCard && <> - {domain.port} - {domain.useSsl ? : } - {domain.useSsl && domain.redirectHttps ? : } - } + {!readonly &&
@@ -100,10 +90,10 @@ export default function DomainsCard({ domains, workloadId, workloadType, readonl
} - {!readonly && + {!readonly && } - +
; } diff --git a/src/components/custom/error-state.tsx b/src/components/custom/error-state.tsx index c573aad0..5b42caad 100644 --- a/src/components/custom/error-state.tsx +++ b/src/components/custom/error-state.tsx @@ -48,6 +48,7 @@ export default function ErrorState({ )} } -
+ ; } diff --git a/src/components/custom/submit-button.tsx b/src/components/custom/submit-button.tsx index be6e7e02..2ee5b0fe 100644 --- a/src/components/custom/submit-button.tsx +++ b/src/components/custom/submit-button.tsx @@ -7,8 +7,9 @@ import { Button } from "../ui/button"; export function SubmitButton(props: { children: React.ReactNode, variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null | undefined, - className?: string + className?: string, + form?: string, }) { const { pending } = useFormStatus(); - return + return } diff --git a/src/components/custom/workload-builds-table.tsx b/src/components/custom/workload-builds-table.tsx index 0ed402d4..13c05b52 100644 --- a/src/components/custom/workload-builds-table.tsx +++ b/src/components/custom/workload-builds-table.tsx @@ -10,7 +10,7 @@ import FullLoadingSpinner from "@/components/ui/full-loading-spinnter"; import { SimpleDataTable } from "@/components/custom/simple-data-table"; import ShortCommitHash from "@/components/custom/short-commit-hash"; import BuildStatusBadge from "@/app/builds/build-status-badge"; -import { BuildLogsDialogContent } from "@/app/project/app/[appId]/overview/build-logs-overlay"; +import { BuildLogsDialogContent } from "@/app/project/workload-components/build-logs-dialog-content"; import { formatDateTime } from "@/frontend/utils/format.utils"; import { Toast } from "@/frontend/utils/toast.utils"; import { useConfirmDialog, useDialog } from "@/frontend/states/zustand.states"; diff --git a/src/shared/utils/path-builder.utils.unit.spec.ts b/src/shared/utils/path-builder.utils.unit.spec.ts index 7f2ebd39..527b0d63 100644 --- a/src/shared/utils/path-builder.utils.unit.spec.ts +++ b/src/shared/utils/path-builder.utils.unit.spec.ts @@ -1,5 +1,5 @@ import { PathBuilderUtils } from './path-builder.utils'; -import { DrawerSessionUtils } from '@/app/project/[projectId]/app-components/project-network-graph/project-network-graph-drawer-session'; +import { DrawerSessionUtils } from '@/app/project/[projectId]/overview/network-graph/project-network-graph-drawer-session'; describe('PathBuilderUtils.projectAppDrawer', () => { it('builds an encoded drawer URL with the default tab', () => {