Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
425c8b4
Add Profile page
rmnbrd Sep 22, 2026
510b3c4
Implement main Profile page UI elements
rmnbrd Sep 22, 2026
7048e55
fix(clusters): disable unavailable cluster profile controls
rmnbrd Sep 22, 2026
14bdda4
feat(clusters): support dynamic platform configuration
rmnbrd Sep 22, 2026
491632f
feat(clusters): add the cluster profile layer sidebar
rmnbrd Sep 23, 2026
9a35d57
feat(clusters): arrange profile inputs in compact rows
rmnbrd Sep 23, 2026
ceedb39
feat(clusters): show positive empty state for unconfigured components
rmnbrd Sep 23, 2026
0c3a0ab
fix(clusters): constrain profile page to its container
rmnbrd Sep 23, 2026
8e26916
fix(clusters): match profile skeleton to configuration rows
rmnbrd Sep 23, 2026
011a43d
chore(clusters): log profile schema responses in development
rmnbrd Sep 23, 2026
140e228
fix(clusters): preserve resolved profile fields on refresh
rmnbrd Sep 23, 2026
9047d07
fix(clusters): match profile tab icons to sidebar
rmnbrd Sep 23, 2026
f4b141f
fix(clusters): remove operator footer from profile sidebar
rmnbrd Sep 24, 2026
9488c60
feat(clusters): support nested profile configuration fields
rmnbrd Sep 24, 2026
0b4e6aa
feat(clusters): remove heading
rmnbrd Sep 24, 2026
c8b64f6
fix(clusters): use section for profile content region
rmnbrd Sep 24, 2026
3347ef1
feat(clusters): add searchable cluster profile settings
rmnbrd Sep 24, 2026
ace665a
feat(clusters): save and deploy profile changes
rmnbrd Sep 25, 2026
1baf3fd
refactor(clusters): move profile state into context
rmnbrd Sep 25, 2026
3c3c077
fix(shared-ui): order highlight text classes consistently
rmnbrd Sep 25, 2026
cc2b245
fix(clusters): remove profile header surface background
rmnbrd Sep 25, 2026
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
10 changes: 10 additions & 0 deletions apps/console/public/assets/sidebar-test/layer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions apps/console/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import { Route as AuthenticatedOrganizationOrganizationIdSettingsAgentsTokensRou
import { Route as AuthenticatedOrganizationOrganizationIdSettingsAgentsMcpsRouteImport } from './routes/_authenticated/organization/$organizationId/settings/agents/mcps'
import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdVariablesRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/variables'
import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/overview'
import { Route as AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRouteImport } from './routes/_authenticated/organization/$organizationId/cluster/$clusterId/profile'
import { Route as AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRouteImport } from './routes/_authenticated/organization/$organizationId/cluster/$clusterId/overview'
import { Route as AuthenticatedOrganizationOrganizationIdClusterClusterIdClusterLogsRouteImport } from './routes/_authenticated/organization/$organizationId/cluster/$clusterId/cluster-logs'
import { Route as AuthenticatedOrganizationOrganizationIdClusterClusterIdCloudShellRouteImport } from './routes/_authenticated/organization/$organizationId/cluster/$clusterId/cloud-shell'
Expand Down Expand Up @@ -582,6 +583,14 @@ const AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRoute =
getParentRoute: () => AuthenticatedOrganizationOrganizationIdRouteRoute,
} as any,
)
const AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRoute =
AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRouteImport.update(
{
id: '/cluster/$clusterId/profile',
path: '/cluster/$clusterId/profile',
getParentRoute: () => AuthenticatedOrganizationOrganizationIdRouteRoute,
} as any,
)
const AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRoute =
AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRouteImport.update(
{
Expand Down Expand Up @@ -1936,6 +1945,7 @@ export interface FileRoutesByFullPath {
'/organization/$organizationId/cluster/$clusterId/cloud-shell': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdCloudShellRoute
'/organization/$organizationId/cluster/$clusterId/cluster-logs': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdClusterLogsRoute
'/organization/$organizationId/cluster/$clusterId/overview': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRoute
'/organization/$organizationId/cluster/$clusterId/profile': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRoute
'/organization/$organizationId/project/$projectId/overview': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRoute
'/organization/$organizationId/project/$projectId/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdVariablesRoute
'/organization/$organizationId/settings/agents/mcps': typeof AuthenticatedOrganizationOrganizationIdSettingsAgentsMcpsRoute
Expand Down Expand Up @@ -2131,6 +2141,7 @@ export interface FileRoutesByTo {
'/organization/$organizationId/cluster/$clusterId/cloud-shell': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdCloudShellRoute
'/organization/$organizationId/cluster/$clusterId/cluster-logs': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdClusterLogsRoute
'/organization/$organizationId/cluster/$clusterId/overview': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRoute
'/organization/$organizationId/cluster/$clusterId/profile': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRoute
'/organization/$organizationId/project/$projectId/overview': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRoute
'/organization/$organizationId/project/$projectId/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdVariablesRoute
'/organization/$organizationId/settings/agents/mcps': typeof AuthenticatedOrganizationOrganizationIdSettingsAgentsMcpsRoute
Expand Down Expand Up @@ -2321,6 +2332,7 @@ export interface FileRoutesById {
'/_authenticated/organization/$organizationId/cluster/$clusterId/cloud-shell': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdCloudShellRoute
'/_authenticated/organization/$organizationId/cluster/$clusterId/cluster-logs': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdClusterLogsRoute
'/_authenticated/organization/$organizationId/cluster/$clusterId/overview': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRoute
'/_authenticated/organization/$organizationId/cluster/$clusterId/profile': typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRoute
'/_authenticated/organization/$organizationId/project/$projectId/overview': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRoute
'/_authenticated/organization/$organizationId/project/$projectId/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdVariablesRoute
'/_authenticated/organization/$organizationId/settings/agents/mcps': typeof AuthenticatedOrganizationOrganizationIdSettingsAgentsMcpsRoute
Expand Down Expand Up @@ -2526,6 +2538,7 @@ export interface FileRouteTypes {
| '/organization/$organizationId/cluster/$clusterId/cloud-shell'
| '/organization/$organizationId/cluster/$clusterId/cluster-logs'
| '/organization/$organizationId/cluster/$clusterId/overview'
| '/organization/$organizationId/cluster/$clusterId/profile'
| '/organization/$organizationId/project/$projectId/overview'
| '/organization/$organizationId/project/$projectId/variables'
| '/organization/$organizationId/settings/agents/mcps'
Expand Down Expand Up @@ -2721,6 +2734,7 @@ export interface FileRouteTypes {
| '/organization/$organizationId/cluster/$clusterId/cloud-shell'
| '/organization/$organizationId/cluster/$clusterId/cluster-logs'
| '/organization/$organizationId/cluster/$clusterId/overview'
| '/organization/$organizationId/cluster/$clusterId/profile'
| '/organization/$organizationId/project/$projectId/overview'
| '/organization/$organizationId/project/$projectId/variables'
| '/organization/$organizationId/settings/agents/mcps'
Expand Down Expand Up @@ -2910,6 +2924,7 @@ export interface FileRouteTypes {
| '/_authenticated/organization/$organizationId/cluster/$clusterId/cloud-shell'
| '/_authenticated/organization/$organizationId/cluster/$clusterId/cluster-logs'
| '/_authenticated/organization/$organizationId/cluster/$clusterId/overview'
| '/_authenticated/organization/$organizationId/cluster/$clusterId/profile'
| '/_authenticated/organization/$organizationId/project/$projectId/overview'
| '/_authenticated/organization/$organizationId/project/$projectId/variables'
| '/_authenticated/organization/$organizationId/settings/agents/mcps'
Expand Down Expand Up @@ -3443,6 +3458,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRouteImport
parentRoute: typeof AuthenticatedOrganizationOrganizationIdRouteRoute
}
'/_authenticated/organization/$organizationId/cluster/$clusterId/profile': {
id: '/_authenticated/organization/$organizationId/cluster/$clusterId/profile'
path: '/cluster/$clusterId/profile'
fullPath: '/organization/$organizationId/cluster/$clusterId/profile'
preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRouteImport
parentRoute: typeof AuthenticatedOrganizationOrganizationIdRouteRoute
}
'/_authenticated/organization/$organizationId/cluster/$clusterId/overview': {
id: '/_authenticated/organization/$organizationId/cluster/$clusterId/overview'
path: '/cluster/$clusterId/overview'
Expand Down Expand Up @@ -5179,6 +5201,7 @@ interface AuthenticatedOrganizationOrganizationIdRouteRouteChildren {
AuthenticatedOrganizationOrganizationIdClusterClusterIdCloudShellRoute: typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdCloudShellRoute
AuthenticatedOrganizationOrganizationIdClusterClusterIdClusterLogsRoute: typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdClusterLogsRoute
AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRoute: typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRoute
AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRoute: typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRoute
AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRoute
AuthenticatedOrganizationOrganizationIdProjectProjectIdVariablesRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdVariablesRoute
AuthenticatedOrganizationOrganizationIdClusterClusterIdIndexRoute: typeof AuthenticatedOrganizationOrganizationIdClusterClusterIdIndexRoute
Expand Down Expand Up @@ -5248,6 +5271,8 @@ const AuthenticatedOrganizationOrganizationIdRouteRouteChildren: AuthenticatedOr
AuthenticatedOrganizationOrganizationIdClusterClusterIdClusterLogsRoute,
AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRoute:
AuthenticatedOrganizationOrganizationIdClusterClusterIdOverviewRoute,
AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRoute:
AuthenticatedOrganizationOrganizationIdClusterClusterIdProfileRoute,
AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRoute:
AuthenticatedOrganizationOrganizationIdProjectProjectIdOverviewRoute,
AuthenticatedOrganizationOrganizationIdProjectProjectIdVariablesRoute:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Navigate, createFileRoute, useParams } from '@tanstack/react-router'
import { useFeatureFlagEnabled } from 'posthog-js/react'
import { z } from 'zod'
import { ClusterProfileFeature, ENGINE_V2_PLATFORM_CONFIGURATION_FEATURE_FLAG } from '@qovery/domains/clusters/feature'
import { useDocumentTitle } from '@qovery/shared/util-hooks'

export const Route = createFileRoute('/_authenticated/organization/$organizationId/cluster/$clusterId/profile')({
component: RouteComponent,
validateSearch: z.object({
component: z.string().optional(),
search: z.string().optional(),
}),
})

function RouteComponent() {
useDocumentTitle('Cluster - Profile')
const { organizationId = '', clusterId = '' } = useParams({ strict: false })
const { component, search } = Route.useSearch()
const navigate = Route.useNavigate()
const isProfileEnabled = Boolean(useFeatureFlagEnabled(ENGINE_V2_PLATFORM_CONFIGURATION_FEATURE_FLAG))

if (!isProfileEnabled && organizationId && clusterId) {
return (
<Navigate
to="/organization/$organizationId/cluster/$clusterId/overview"
params={{ organizationId, clusterId }}
replace
/>
)
}

return (
<ClusterProfileFeature
activeComponentKey={component}
search={search}
onActiveComponentChange={(nextComponent) =>
navigate({ search: (previous) => ({ ...previous, component: nextComponent }) })
}
onSearchChange={(nextSearch) =>
navigate({ search: (previous) => ({ ...previous, search: nextSearch || undefined }), replace: true })
}
/>
)
}
16 changes: 14 additions & 2 deletions apps/console/src/routes/_authenticated/organization/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import posthog from 'posthog-js'
import { useFeatureFlagEnabled } from 'posthog-js/react'
import { type Cluster } from 'qovery-typescript-axios'
import { Suspense, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useClusters } from '@qovery/domains/clusters/feature'
import { ENGINE_V2_PLATFORM_CONFIGURATION_FEATURE_FLAG, useClusters } from '@qovery/domains/clusters/feature'
import { useEnvironment } from '@qovery/domains/environments/feature'
import { useProject } from '@qovery/domains/projects/feature'
import { type AnyService, isAgenticWorkflow, isArgoCd, isManagedDatabase } from '@qovery/domains/services/data-access'
Expand Down Expand Up @@ -97,6 +97,12 @@ const CLUSTER_TABS: NavigationTab[] = [
iconName: 'terminal',
routeId: '/_authenticated/organization/$organizationId/cluster/$clusterId/cloud-shell',
},
{
id: 'profile',
label: 'Profile',
iconName: 'layer-group',
routeId: '/_authenticated/organization/$organizationId/cluster/$clusterId/profile',
},
{
id: 'settings',
label: 'Settings',
Expand Down Expand Up @@ -335,6 +341,9 @@ function useNavigationContext(): NavigationContext | null {
const pathname = location.pathname
const organizationId = typeof params.organizationId === 'string' ? params.organizationId : ''
const isAgenticWorkflowEnabled = Boolean(useFeatureFlagEnabled('argentic-workflow'))
const isEngineV2PlatformConfigurationEnabled = Boolean(
useFeatureFlagEnabled(ENGINE_V2_PLATFORM_CONFIGURATION_FEATURE_FLAG)
)
const { data: service } = useServiceSummary({
environmentId: params.environmentId,
serviceId: params.serviceId,
Expand Down Expand Up @@ -374,7 +383,9 @@ function useNavigationContext(): NavigationContext | null {
? getServiceTabs(service, currentCluster, isAgenticWorkflowEnabled)
: context.type === 'organization'
? context.tabs.filter((tab) => hasAlerting || tab.id !== 'alerts')
: context.tabs
: context.type === 'cluster'
? context.tabs.filter((tab) => isEngineV2PlatformConfigurationEnabled || tab.id !== 'profile')
: context.tabs

return {
type: context.type,
Expand Down Expand Up @@ -472,6 +483,7 @@ const fullWidthRouteIds: FileRouteTypes['id'][] = [
'/_authenticated/organization/$organizationId/cluster/$clusterId/cluster-logs',
'/_authenticated/organization/$organizationId/cluster/$clusterId/deployments/logs/$deploymentId',
'/_authenticated/organization/$organizationId/cluster/$clusterId/cloud-shell',
'/_authenticated/organization/$organizationId/cluster/$clusterId/profile',
'/_authenticated/organization/$organizationId/cluster/$clusterId/settings',
'/_authenticated/organization/$organizationId/project/$projectId/settings',
'/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/settings',
Expand Down
2 changes: 2 additions & 0 deletions libs/domains/clusters/data-access/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './lib/domains-clusters-data-access'
export * from './lib/http/is-http-status'
export * from './lib/platform-configuration/platform-configuration'
export { isGcpCluster } from './lib/cluster-checks/is-gcp-cluster'
export { isAwsCluster } from './lib/cluster-checks/is-aws-cluster'
export { excludeSecretManagerAccess } from './lib/secret-manager/exclude-secret-manager-access'
Expand Down
14 changes: 14 additions & 0 deletions libs/domains/clusters/data-access/src/lib/http/is-http-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { isAxiosError } from 'axios'

export function isHttpStatus(error: unknown, status: number) {
if (isAxiosError(error)) return error.response?.status === status
return Boolean(
error &&
typeof error === 'object' &&
'response' in error &&
error.response &&
typeof error.response === 'object' &&
'status' in error.response &&
error.response.status === status
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createQueryKeys } from '@lukemorales/query-key-factory'
import {
type ClusterPlatformBindingRequest,
type PlatformCloudVendor,
type PlatformClusterMode,
type PlatformComponentConfigurationPreviewRequest,
PlatformConfigurationApi,
} from 'qovery-typescript-axios'
import { isHttpStatus } from '../http/is-http-status'

const platformConfigurationApi = new PlatformConfigurationApi()

export const platformConfiguration = createQueryKeys('platformConfiguration', {
templates: ({
organizationId,
clusterMode,
cloudProvider,
}: {
organizationId: string
clusterMode?: PlatformClusterMode
cloudProvider?: PlatformCloudVendor
}) => ({
queryKey: [organizationId, clusterMode, cloudProvider],
async queryFn() {
const response = await platformConfigurationApi.listPlatformTemplates(organizationId, clusterMode, cloudProvider)
return response.data.results
},
}),
binding: ({ organizationId, clusterId }: { organizationId: string; clusterId: string }) => ({
queryKey: [organizationId, clusterId],
async queryFn() {
try {
const response = await platformConfigurationApi.getClusterPlatformBinding(organizationId, clusterId)
return response.data
} catch (error) {
if (isHttpStatus(error, 404)) return null
throw error
}
},
}),
componentConfiguration: ({
organizationId,
clusterId,
componentKey,
request,
}: {
organizationId: string
clusterId: string
componentKey: string
request: PlatformComponentConfigurationPreviewRequest
}) => ({
queryKey: [organizationId, clusterId, componentKey, request],
async queryFn() {
const response = await platformConfigurationApi.resolvePlatformComponentConfiguration(
organizationId,
clusterId,
componentKey,
request
)
return response.data
},
}),
})

export const platformConfigurationMutations = {
async updateBinding({
organizationId,
clusterId,
bindingRequest,
}: {
organizationId: string
clusterId: string
bindingRequest: ClusterPlatformBindingRequest
}) {
const response = await platformConfigurationApi.updateClusterPlatformBinding(
organizationId,
clusterId,
bindingRequest
)
return response.data
},
}
5 changes: 5 additions & 0 deletions libs/domains/clusters/feature/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './lib/cluster-actions/cluster-actions'
export * from './lib/cluster-avatar/cluster-avatar'
export * from './lib/cluster-advanced-settings/cluster-advanced-settings'
export * from './lib/cluster-advanced-settings/cluster-advanced-settings-feature'
export * from './lib/cluster-profile/cluster-profile'
export * from './lib/cluster-advanced-settings/init-form-values'
export * from './lib/cluster-setup/cluster-setup'
export * from './lib/cluster-need-redeploy-flag/cluster-need-redeploy-flag'
Expand Down Expand Up @@ -56,6 +57,10 @@ export * from './lib/hooks/use-cluster-logs/use-cluster-logs'
export * from './lib/hooks/use-cluster-status/use-cluster-status'
export * from './lib/hooks/use-cluster-statuses/use-cluster-statuses'
export * from './lib/hooks/use-cluster/use-cluster'
export * from './lib/hooks/use-platform-templates/use-platform-templates'
export * from './lib/platform-configuration/hooks/use-platform-binding'
export * from './lib/platform-configuration/hooks/use-platform-component-configuration'
export * from './lib/platform-configuration/hooks/use-platform-component-configurations'
export * from './lib/hooks/use-clusters/use-clusters'
export * from './lib/hooks/use-argocd-credentials/use-argocd-credentials'
export * from './lib/hooks/use-cluster-advanced-settings/use-cluster-advanced-settings'
Expand Down
Loading
Loading