Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions apps/console/src/routeTree.gen.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import { createFileRoute, useParams } from '@tanstack/react-router'
import { useFeatureFlagEnabled } from 'posthog-js/react'
import { type CloudProvider, type ClusterRegion, type SecretManagerAccess } from 'qovery-typescript-axios'
import { useMemo, useState } from 'react'
import { useCloudProviders } from '@qovery/domains/cloud-providers/feature'
import {
AddonToggleCard,
SECRET_MANAGER_OPTIONS,
SecretManagerAssociatedServicesModal,
SecretManagerIntegrationModal,
SecretManagerList,
getSecretManagerOption,
useCluster,
useEditCluster,
} from '@qovery/domains/clusters/feature'
import { SettingsHeading } from '@qovery/shared/console-shared'
import {
Badge,
Button,
DropdownMenu,
Icon,
IconFlag,
Section,
useModal,
useModalConfirmation,
} from '@qovery/shared/ui'
import { isGcpCluster } from '@qovery/shared/util-clusters'

export const Route = createFileRoute('/_authenticated/organization/$organizationId/cluster/$clusterId/settings/addons')(
{
component: RouteComponent,
}
)



function RouteComponent() {
const { openModal, closeModal } = useModal()
const { openModalConfirmation } = useModalConfirmation()
const { organizationId = '', clusterId = '' } = useParams({ strict: false })
const secretManagerEnabled = useFeatureFlagEnabled('secret-manager')
const { data: cluster } = useCluster({ organizationId, clusterId, suspense: true })

console.log('secret_manager_accesses', cluster?.secret_manager_accesses)

const { data: cloudProviders = [] } = useCloudProviders({ suspense: true })

const [kedaEnabled, setKedaEnabled] = useState(cluster?.keda?.enabled ?? false)

const { mutateAsync: editCluster, isLoading: isEditClusterLoading } = useEditCluster()

const secretManagerDropdownOptions = useMemo(() => {
if (!isGcpCluster(cluster)) {
return SECRET_MANAGER_OPTIONS
}

const gcpOption = SECRET_MANAGER_OPTIONS.find((option) => option.value === 'GCP_SECRET_MANAGER')
const awsOptions = SECRET_MANAGER_OPTIONS.filter((option) => option.value !== 'GCP_SECRET_MANAGER')
return gcpOption ? [gcpOption, ...awsOptions] : SECRET_MANAGER_OPTIONS
}, [cluster])

const currentProvider = useMemo(
() => cloudProviders.find((cloud) => cloud.short_name === cluster?.cloud_provider),
[cloudProviders, cluster?.cloud_provider]
)
const regionOptions = useMemo(
() =>
(currentProvider as CloudProvider | undefined)?.regions?.map((region: ClusterRegion) => ({
label: `${region.city} (${region.name})`,
value: region.name,
icon: <IconFlag code={region.country_code} />,
})) || [],
[currentProvider]
)

const [secretManagers, setSecretManagers] = useState<SecretManagerAccess[]>(
() => cluster?.secret_manager_accesses ?? []
)

const openSecretManagerModal = (option: typeof SECRET_MANAGER_OPTIONS[number], secretManager?: SecretManagerAccess) => {
openModal({
content: (
<SecretManagerIntegrationModal
option={option}
regionOptions={regionOptions}
cluster={cluster}
mode={secretManager ? 'edit' : 'create'}
initialValues={secretManager}
onClose={closeModal}
onSubmit={(payload) => {
setSecretManagers((prev) => {
if (secretManager) {
return prev.map((item) =>
item.id === secretManager.id
? {
...payload,
// usedByServices: integration.usedByServices ?? 0,
// associatedItems: integration.associatedItems,
}
: item
)
}
return [
...prev,
{
...payload,
// usedByServices: 0
},
]
})
}}
/>
),
options: {
width: 676,
fakeModal: true,
},
})
}

const handleDeleteSecretManager = (integration: SecretManagerAccess) => {
openModalConfirmation({
title: 'Delete secret manager',
name: integration.name,
confirmationMethod: 'action',
confirmationAction: 'delete',
action: () => {
// TODO [secret-manager] Double check if secret manager is used by any service

// const hasSecrets = (integration.usedByServices ?? 0) > 0
// if (hasSecrets) {
// openDeletionHelper(integration)
// } else {
// }

setSecretManagers((prev) => prev.filter((item) => item.id !== integration.id))
},
})
}

const openSecretManagerAssociatedServicesModal = (integration: SecretManagerAccess) => {
openModal({
content: (
<SecretManagerAssociatedServicesModal
// associatedItems={integration.associatedItems ?? []}
associatedItems={[]}
organizationId={organizationId}
title="Associated services"
description={`${integration.name} is referenced by the following environments and services.`}
onClose={closeModal}
/>
),
options: {
fakeModal: true,
},
})
}

const handleSave = async () => {
if (!cluster) return

const updatedCluster = {
...cluster,
secret_manager_accesses: secretManagers,
keda: {
enabled: isGcpCluster(cluster) ? false : kedaEnabled,
},
}

try {
await editCluster({
organizationId,
clusterId: cluster.id,
clusterRequest: updatedCluster,
})
} catch (error) {
console.error(error)
}
}

return (
<div className="flex w-full flex-col justify-between">
<Section className="p-8">
<SettingsHeading
title="Add-ons"
description="Add-ons are activable options that will grant you access to specific Qovery feature. You can activate or deactivate them when you want."
/>
<div className="max-w-content-with-navigation-left">
<div className="divide-y divide-neutral overflow-hidden rounded-lg border border-neutral bg-surface-neutral shadow-[0_0_4px_0_rgba(0,0,0,0.01),0_2px_3px_0_rgba(0,0,0,0.02)]">
<div className="p-4">
<AddonToggleCard
title="KEDA autoscaler"
description="Qovery KEDA autoscaler allows you to add event-based autoscaling on all the services running on this cluster."
badge={{ label: 'Free', color: 'green' }}
activated={kedaEnabled}
onToggle={() => setKedaEnabled((prev) => !prev)}
/>
</div>

{secretManagerEnabled && (
<div className="p-4">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-neutral">Secret manager integration</span>
<Badge size="sm" radius="full" variant="surface" color="green" className="text-[13px]">
Free
</Badge>
</div>
<p className="text-sm text-neutral-subtle">
Link any secret manager on your cluster to add external secrets variables to all the services
running on your cluster
</p>
</div>
<div className="flex flex-col items-start gap-3">
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button color="neutral" variant="solid" size="md" className="gap-2" type="button">
<Icon iconName="circle-plus" iconStyle="regular" className="text-xs" />
Add secret manager
<Icon iconName="chevron-down" className="text-[10px]" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="start">
{secretManagerDropdownOptions.map((option) => (
<DropdownMenu.Item
key={option.value}
color="neutral"
icon={<Icon name={option.icon} width={16} height={16} />}
onSelect={() => openSecretManagerModal(option)}
>
{option.label}
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu.Root>
<SecretManagerList
secretManagers={secretManagers}
onEdit={(manager) =>
openSecretManagerModal(getSecretManagerOption(manager.endpoint.mode), manager)
}
onDelete={handleDeleteSecretManager}
onViewAssociatedServices={openSecretManagerAssociatedServicesModal}
/>
</div>
</div>
</div>
)}
</div>
<div className="mt-8">
<Button type="button" size="lg" onClick={handleSave} loading={isEditClusterLoading}>
Save
</Button>
</div>
</div>
</Section>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ function RouteComponent() {
icon: 'plug' as const,
}

const addonsLink = {
title: 'Add-ons',
to: `${pathSettings}/addons`,
icon: 'puzzle-piece' as const,
}

const advancedSettingsLink = {
title: 'Advanced settings',
to: `${pathSettings}/advanced-settings`,
Expand All @@ -79,8 +85,8 @@ function RouteComponent() {
credentialsLink,
...(eksAnywhereCluster ? [] : [resourcesLink]),
imageRegistryLink,
...(eksAnywhereCluster ? [] : [networkLink]),
...(eksAnywhereCluster ? [] : [advancedSettingsLink]),
...(eksAnywhereCluster ? [] : [networkLink, advancedSettingsLink, addonsLink]),
advancedSettingsLink,
dangerZoneLink,
]
}
Expand All @@ -99,6 +105,7 @@ function RouteComponent() {
credentialsLink,
imageRegistryLink,
networkLink,
addonsLink,
advancedSettingsLink,
dangerZoneLink,
])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { useEffect } from 'react'
import { StepAddons, useMaybeClusterContainerCreateContext } from '@qovery/domains/clusters/feature'
import { useDocumentTitle } from '@qovery/shared/util-hooks'

export const Route = createFileRoute('/_authenticated/organization/$organizationId/cluster/create/$slug/addons')({
component: Addons,
})

function Addons() {
useDocumentTitle('Add-ons - Create Cluster')
const { organizationId = '', slug } = useParams({ strict: false })
const navigate = useNavigate()
const createContext = useMaybeClusterContainerCreateContext()
const generalData = createContext?.generalData

const creationFlowUrl = `/organization/${organizationId}/cluster/create/${slug}`
const isAllowed =
generalData?.installation_type === 'MANAGED' &&
(generalData?.cloud_provider === 'AWS' || generalData?.cloud_provider === 'GCP')
const shouldRedirect = Boolean(generalData) && !isAllowed

useEffect(() => {
if (shouldRedirect) {
navigate({ to: `${creationFlowUrl}/summary` })
}
}, [creationFlowUrl, navigate, shouldRedirect])

if (shouldRedirect) {
return null
}

if (!createContext) {
return null
}

const handleSubmit = () => {
navigate({ to: `${creationFlowUrl}/summary` })
}

return <StepAddons organizationId={organizationId} onSubmit={handleSubmit} />
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { StepFeatures } from '@qovery/domains/clusters/feature'
import { StepFeatures, useClusterContainerCreateContext } from '@qovery/domains/clusters/feature'
import { useDocumentTitle } from '@qovery/shared/util-hooks'

export const Route = createFileRoute('/_authenticated/organization/$organizationId/cluster/create/$slug/features')({
Expand All @@ -10,10 +10,15 @@ function Features() {
useDocumentTitle('Features - Create Cluster')
const { organizationId = '', slug } = useParams({ strict: false })
const navigate = useNavigate()
const { generalData } = useClusterContainerCreateContext()

const creationFlowUrl = `/organization/${organizationId}/cluster/create/${slug}`

const handleSubmit = () => {
if (generalData?.cloud_provider === 'AWS' || generalData?.cloud_provider === 'GCP') {
navigate({ to: `${creationFlowUrl}/addons` })
return
}
navigate({ to: `${creationFlowUrl}/summary` })
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useQuery } from '@tanstack/react-query'
import { queries } from '@qovery/state/util-queries'

export function useCloudProviders() {
export function useCloudProviders({ suspense = false }: { suspense?: boolean } = {}) {
return useQuery({
...queries.cloudProviders.list,
suspense,
})
}

Expand Down
4 changes: 4 additions & 0 deletions libs/domains/clusters/feature/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ export * from './lib/cluster-creation-flow/step-kubeconfig/step-kubeconfig'
export * from './lib/cluster-creation-flow/step-eks/step-eks'
export * from './lib/cluster-creation-flow/step-resources/step-resources'
export * from './lib/cluster-creation-flow/step-features/step-features'
export * from './lib/cluster-creation-flow/step-addons/step-addons'
export * from './lib/cluster-creation-flow/step-summary/step-summary'
export * from './lib/cluster-creation-flow/cluster-creation-flow'
export * from './lib/cluster-addons'
export * from './lib/secret-manager-modals/secret-manager-associated-services-modal'
export * from './lib/secret-manager-modals/secret-manager-integration-modal'
export * from './lib/cluster-general-settings/cluster-general-settings'
export * from './lib/cluster-credentials-settings/cluster-credentials-settings'
export * from './lib/hooks/use-cluster-cloud-provider-info/use-cluster-cloud-provider-info'
Expand Down
Loading
Loading