diff --git a/backend/src/applications/applications.controller.ts b/backend/src/applications/applications.controller.ts index 49a68ea..deb9f00 100644 --- a/backend/src/applications/applications.controller.ts +++ b/backend/src/applications/applications.controller.ts @@ -63,7 +63,7 @@ export class ApplicationsController { @Post() @ApiOperation({ summary: 'Create a new application' }) async create(@Request() req: any, @Body() dto: CreateApplicationDto) { - return this.applicationsService.create(req.user.id, dto, req.user.role); + return this.applicationsService.create(req.user.id, dto); } @Post(':id/upload') diff --git a/backend/src/applications/applications.service.ts b/backend/src/applications/applications.service.ts index 226a63f..6dcb145 100644 --- a/backend/src/applications/applications.service.ts +++ b/backend/src/applications/applications.service.ts @@ -9,7 +9,6 @@ import { Application } from './entities/application.entity'; import { CreateApplicationDto, UpdateApplicationDto } from './dto/application.dto'; import { ClustersService } from '../clusters/clusters.service'; import { - UserRole, DatabaseType, CustomDomainStatus, AppRuntime, @@ -57,39 +56,17 @@ export class ApplicationsService { throw new BadRequestException('Failed to generate a unique subdomain. Please try again.'); } - async create(userId: string, dto: CreateApplicationDto, userRole?: string): Promise { + async create(userId: string, dto: CreateApplicationDto): Promise { dto = normalizeCreateApplicationDto(dto); const productType = dto.productType ?? ProductType.APPLICATION; - // End users and technical staff cannot influence placement; only admins may manually assign. - const isAdmin = userRole === UserRole.ADMIN; - if (!isAdmin) { - if (dto.clusterId || dto.poolId) { - this.logger.warn(`Non-admin user ${userId} attempted manual cluster/pool selection - ignoring`); - } - dto.clusterId = undefined; - dto.poolId = undefined; - } - - let clusterId = dto.clusterId; - let poolId = dto.poolId; - let allocationLogId: string | undefined; - - if (isAdmin && clusterId) { - await this.clustersService.findOne(clusterId); - this.logger.log(`Manual cluster assignment for app "${dto.name}" -> cluster ${clusterId}`); - } else { - const allocationDto = { ...dto }; - if (poolId) { - allocationDto.poolId = poolId; - } - const allocation = await this.clustersService.selectClusterForApplication(allocationDto, userId); - clusterId = allocation.cluster.id; - poolId = allocation.pool?.id || (isAdmin ? poolId : undefined); - allocationLogId = allocation.allocationLogId; - if (!clusterId) { - throw new BadRequestException('No eligible cluster available for this application'); - } + // Placement is always decided automatically by the allocator. + const allocation = await this.clustersService.selectClusterForApplication(dto, userId); + const clusterId = allocation.cluster.id; + const poolId = allocation.pool?.id; + const allocationLogId = allocation.allocationLogId; + if (!clusterId) { + throw new BadRequestException('No eligible cluster available for this application'); } // Generate database credentials if a database is requested diff --git a/backend/src/applications/dto/application.dto.ts b/backend/src/applications/dto/application.dto.ts index 7c53eec..7d92e31 100644 --- a/backend/src/applications/dto/application.dto.ts +++ b/backend/src/applications/dto/application.dto.ts @@ -198,16 +198,6 @@ export class CreateApplicationDto { @IsNumber() port?: number; - @ApiPropertyOptional({ description: 'Admin-only manual cluster override. Ignored for non-admin users.' }) - @IsOptional() - @IsString() - clusterId?: string; - - @ApiPropertyOptional({ description: 'Admin-only pool override. Ignored for non-admin users.' }) - @IsOptional() - @IsString() - poolId?: string; - @ApiPropertyOptional({ example: 'www.example.com', description: 'Custom domain for the application (requires additional fee)' }) @IsOptional() @IsString() diff --git a/backend/src/clusters/clusters.service.ts b/backend/src/clusters/clusters.service.ts index ff1aa95..ffe88a1 100644 --- a/backend/src/clusters/clusters.service.ts +++ b/backend/src/clusters/clusters.service.ts @@ -266,7 +266,7 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy { } async selectClusterForApplication( - dto: CreateApplicationDto, + dto: CreateApplicationDto & { poolId?: string }, userId: string, options: { excludeClusterIds?: string[]; diff --git a/frontend/src/app/[lang]/dashboard/deploy/page.tsx b/frontend/src/app/[lang]/dashboard/deploy/page.tsx index 005fc34..28c03b7 100644 --- a/frontend/src/app/[lang]/dashboard/deploy/page.tsx +++ b/frontend/src/app/[lang]/dashboard/deploy/page.tsx @@ -13,8 +13,6 @@ import { useDeployProgressActions } from '@/lib/use-deploy-progress-actions'; import { notify } from '@/lib/notify'; import type { CreateApplicationDto, - ClusterPublic, - ClusterPoolPublic, DeployCostPreview, BillingCycle, PricingCatalog, @@ -251,7 +249,6 @@ export default function DeployPage() { useDeployProgressStore.getState().stopTracking(appId); } }; - const isAdmin = user?.role === 'admin'; const [step, setStep] = useState(0); const [form, setForm] = useState({ name: '', @@ -289,7 +286,6 @@ export default function DeployPage() { const [zipFile, setZipFile] = useState(null); const [uploadProgress, setUploadProgress] = useState(0); const [isDragging, setIsDragging] = useState(false); - const [clusterMode, setClusterMode] = useState<'default' | 'manual' | 'pool'>('default'); const [showDbPassword, setShowDbPassword] = useState(false); const fileInputRef = useRef(null); const [dbDumpFile, setDbDumpFile] = useState(null); @@ -336,18 +332,6 @@ export default function DeployPage() { }; }; - const { data: clusters = [] } = useQuery({ - queryKey: ['clusters-public'], - queryFn: () => api.get('/clusters/public').then((r) => r.data), - enabled: isAdmin, - }); - - const { data: pools = [] } = useQuery({ - queryKey: ['pools-public'], - queryFn: () => api.get('/clusters/pools/public').then((r) => r.data), - enabled: isAdmin, - }); - const { data: pricingCatalog } = useQuery({ queryKey: ['pricing-catalog'], queryFn: () => api.get('/billing/pricing-catalog').then((r) => r.data), @@ -441,10 +425,6 @@ export default function DeployPage() { : {}), ...(form.appStorageSize ? { appStorageSize: `${parseInt(form.appStorageSize, 10) || 2}Gi` } : {}), }); - if (!isAdmin) { - delete payload.clusterId; - delete payload.poolId; - } const res = await api.post('/applications', payload); const appId = res.data.id; @@ -524,10 +504,6 @@ export default function DeployPage() { : {}), ...(form.appStorageSize ? { appStorageSize: `${parseInt(form.appStorageSize, 10) || 2}Gi` } : {}), }); - if (!isAdmin) { - delete payload.clusterId; - delete payload.poolId; - } const res = await api.post('/applications', payload); const appId = res.data.id; @@ -684,10 +660,6 @@ export default function DeployPage() { if (enableCustomDomain && customDomainInput.trim()) { payload.customDomain = customDomainInput.trim(); } - if (!isAdmin) { - delete payload.clusterId; - delete payload.poolId; - } createMutation.mutate(sanitizePayloadForWordPressRuntime(payload)); }; @@ -2101,181 +2073,6 @@ export default function DeployPage() {

{dw.resourcesConfig}

- {/* Cluster Assignment Mode — Super Admin only */} - {isAdmin && ( -
- -
- - - -
- - {/* Manual: show cluster list */} - {clusterMode === 'manual' && ( -
- {clusters.length === 0 ? ( -

{dw.noClustersAvailable}

- ) : ( - clusters.map((cluster) => ( - - )) - )} -
- )} - - {/* Pool: show pool list */} - {clusterMode === 'pool' && ( -
- {pools.length === 0 ? ( -
-

{dw.noClusterPools}

-

{dw.askAdminPool}

-
- ) : ( - pools.map((pool) => ( - - )) - )} -
- )} - - {/* Default: info text */} - {clusterMode === 'default' && ( -
-
- -
-

{dw.defaultClusterWillBeUsed}

-

- Your app will be deployed to the platform's default cluster - {clusters.find((c) => c.isDefault) && ( - <> — {clusters.find((c) => c.isDefault)?.name} - )} -

-
-
-
- )} -
- )} -

{dw.applicationWorkload}

@@ -2609,18 +2406,6 @@ export default function DeployPage() { {dw.tokenProvided}
)} - {isAdmin && ( -
- {dw.cluster} - - {clusterMode === 'manual' && form.clusterId - ? `${clusters.find((c) => c.id === form.clusterId)?.name || form.clusterId}` - : clusterMode === 'pool' && form.poolId - ? `${pools.find((p) => p.id === form.poolId)?.name || 'Pool'} (Load Balanced)` - : dw.automaticAllocator} - -
- )}
{dw.cpu} {form.cpuRequest} / {form.cpuLimit} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index c0e158c..4e2f229 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -246,8 +246,6 @@ export interface CreateApplicationDto { memoryLimit?: string; replicas?: number; port?: number; - clusterId?: string; - poolId?: string; customDomain?: string; // Optional services enableRedis?: boolean;