feat: version selection for runtimes and databases

Backend:
- Add runtimeVersion, phpVersion, dbVersion columns to Application entity
- Add version fields to CreateApplicationDto with validation
- Node.js Dockerfile: use selected version (22/20/18/16) instead of hardcoded 20
- Laravel Dockerfile: use selected PHP version (8.4/8.3/8.2/8.1) instead of 8.3
- WordPress Dockerfile: use selected WP version (6.7/6.6/6.5/6.4) + PHP version
- K8s deployDatabase(): use selected DB version instead of hardcoded postgres:16/mysql:8.0
- K8s restoreDatabaseDump(): match DB image version for restore jobs
- Add dbVersion to ManifestContext interface

Frontend:
- Add runtimeVersion, phpVersion, dbVersion to Application and CreateApplicationDto
- Deploy page: Node.js version dropdown (22/20/18/16)
- Deploy page: Laravel PHP version dropdown (8.4/8.3/8.2/8.1)
- Deploy page: WordPress version + PHP version dropdowns
- Deploy page: PostgreSQL version dropdown (17/16/15/14)
- Deploy page: MySQL version dropdown (9.0/8.4/8.0/5.7)
- Deploy page: auto-set default versions on runtime/DB selection
- Review step: show selected versions
- App detail page: display runtime + DB versions in config and header
This commit is contained in:
keyhan
2026-04-06 23:38:23 +03:30
parent c411c5873e
commit 762657f9ed
8 changed files with 172 additions and 19 deletions
+11 -3
View File
@@ -368,7 +368,7 @@ export default function AppDetailPage() {
</span>
</div>
<p className="text-sm text-gray-500 truncate">
{app.runtime} · {app.subdomain}.apps.cloudhost.local
{app.runtime}{app.runtimeVersion ? ` v${app.runtimeVersion}` : ''}{app.phpVersion ? ` — PHP ${app.phpVersion}` : ''} · {app.subdomain}.apps.cloudhost.local
</p>
</div>
</div>
@@ -419,11 +419,19 @@ export default function AppDetailPage() {
<dl className="space-y-3">
<div className="flex justify-between">
<dt className="text-sm text-gray-500">Runtime</dt>
<dd className="text-sm font-medium text-gray-900">{app.runtime}</dd>
<dd className="text-sm font-medium text-gray-900">
{app.runtime}
{app.runtime === 'nodejs' && app.runtimeVersion ? ` v${app.runtimeVersion}` : ''}
{app.runtime === 'wordpress' && app.runtimeVersion ? ` v${app.runtimeVersion}` : ''}
{(app.runtime === 'laravel' || app.runtime === 'wordpress') && app.phpVersion ? ` — PHP ${app.phpVersion}` : ''}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-sm text-gray-500">Database</dt>
<dd className="text-sm font-medium text-gray-900">{app.databaseType}</dd>
<dd className="text-sm font-medium text-gray-900">
{app.databaseType}
{app.databaseType !== 'none' && app.dbVersion ? ` v${app.dbVersion}` : ''}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-sm text-gray-500">Replicas</dt>
+114 -9
View File
@@ -21,6 +21,9 @@ export default function DeployPage() {
description: '',
runtime: 'nodejs',
databaseType: 'none',
runtimeVersion: '20',
phpVersion: '',
dbVersion: '',
gitUrl: '',
gitToken: '',
gitBranch: '',
@@ -366,17 +369,17 @@ export default function DeployPage() {
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{[
{ value: 'nodejs', label: 'Node.js', icon: <Hexagon className="w-6 h-6 text-green-500" />, desc: 'Express, NestJS, Fastify...' },
{ value: 'laravel', label: 'Laravel', icon: <Hexagon className="w-6 h-6 text-orange-500" />, desc: 'PHP 8.3, Composer, Artisan' },
{ value: 'wordpress', label: 'WordPress', icon: <Hexagon className="w-6 h-6 text-blue-600" />, desc: 'PHP 8.3, Apache, wp-content' },
{ value: 'laravel', label: 'Laravel', icon: <Hexagon className="w-6 h-6 text-orange-500" />, desc: 'PHP, Composer, Artisan' },
{ value: 'wordpress', label: 'WordPress', icon: <Hexagon className="w-6 h-6 text-blue-600" />, desc: 'PHP, Apache, wp-content' },
].map((opt) => (
<button
key={opt.value}
type="button"
onClick={() => {
const updates: any = { runtime: opt.value as any };
if (opt.value === 'nodejs') updates.port = 3000;
else if (opt.value === 'laravel') updates.port = 8000;
else if (opt.value === 'wordpress') { updates.port = 80; updates.databaseType = 'mysql'; }
const updates: any = { runtime: opt.value as any, phpVersion: '' };
if (opt.value === 'nodejs') { updates.port = 3000; updates.runtimeVersion = '20'; }
else if (opt.value === 'laravel') { updates.port = 8000; updates.runtimeVersion = ''; updates.phpVersion = '8.3'; }
else if (opt.value === 'wordpress') { updates.port = 80; updates.databaseType = 'mysql'; updates.runtimeVersion = '6.7'; updates.phpVersion = '8.3'; }
setForm({ ...form, ...updates });
}}
className={`p-4 rounded-xl border-2 text-left transition-colors ${
@@ -390,6 +393,70 @@ export default function DeployPage() {
))}
</div>
</div>
{/* Runtime Version Selectors */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{form.runtime === 'nodejs' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Node.js Version</label>
<select
className="input-field"
value={form.runtimeVersion || '20'}
onChange={(e) => setForm({ ...form, runtimeVersion: e.target.value })}
>
<option value="22">Node.js 22 (LTS)</option>
<option value="20">Node.js 20 (LTS)</option>
<option value="18">Node.js 18</option>
<option value="16">Node.js 16</option>
</select>
</div>
)}
{form.runtime === 'laravel' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">PHP Version</label>
<select
className="input-field"
value={form.phpVersion || '8.3'}
onChange={(e) => setForm({ ...form, phpVersion: e.target.value })}
>
<option value="8.4">PHP 8.4</option>
<option value="8.3">PHP 8.3 (Recommended)</option>
<option value="8.2">PHP 8.2</option>
<option value="8.1">PHP 8.1</option>
</select>
</div>
)}
{form.runtime === 'wordpress' && (
<>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">WordPress Version</label>
<select
className="input-field"
value={form.runtimeVersion || '6.7'}
onChange={(e) => setForm({ ...form, runtimeVersion: e.target.value })}
>
<option value="6.7">WordPress 6.7 (Latest)</option>
<option value="6.6">WordPress 6.6</option>
<option value="6.5">WordPress 6.5</option>
<option value="6.4">WordPress 6.4</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">PHP Version</label>
<select
className="input-field"
value={form.phpVersion || '8.3'}
onChange={(e) => setForm({ ...form, phpVersion: e.target.value })}
>
<option value="8.3">PHP 8.3 (Recommended)</option>
<option value="8.2">PHP 8.2</option>
<option value="8.1">PHP 8.1</option>
</select>
</div>
</>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Database
@@ -410,7 +477,7 @@ export default function DeployPage() {
key={opt.value}
type="button"
disabled={disabled}
onClick={() => setForm({ ...form, databaseType: opt.value as any })}
onClick={() => setForm({ ...form, databaseType: opt.value as any, dbVersion: opt.value === 'postgresql' ? '16' : opt.value === 'mysql' ? '8.0' : '' })}
className={`p-4 rounded-xl border-2 text-center transition-colors ${
form.databaseType === opt.value ? 'border-primary-500 bg-primary-50' : 'border-gray-200 hover:border-gray-300'
} ${disabled ? 'opacity-40 cursor-not-allowed' : ''}`}
@@ -423,6 +490,36 @@ export default function DeployPage() {
</div>
</div>
{/* Database Version — shown when a DB is selected */}
{form.databaseType !== 'none' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{form.databaseType === 'postgresql' ? 'PostgreSQL' : 'MySQL'} Version
</label>
<select
className="input-field max-w-xs"
value={form.dbVersion || (form.databaseType === 'postgresql' ? '16' : '8.0')}
onChange={(e) => setForm({ ...form, dbVersion: e.target.value })}
>
{form.databaseType === 'postgresql' ? (
<>
<option value="17">PostgreSQL 17 (Latest)</option>
<option value="16">PostgreSQL 16 (LTS)</option>
<option value="15">PostgreSQL 15</option>
<option value="14">PostgreSQL 14</option>
</>
) : (
<>
<option value="9.0">MySQL 9.0 (Latest)</option>
<option value="8.4">MySQL 8.4 (LTS)</option>
<option value="8.0">MySQL 8.0</option>
<option value="5.7">MySQL 5.7</option>
</>
)}
</select>
</div>
)}
{/* Database Credentials — shown when a DB is selected */}
{form.databaseType !== 'none' && (
<div className="p-4 bg-blue-50/50 border border-blue-200 rounded-xl space-y-4">
@@ -828,11 +925,19 @@ export default function DeployPage() {
</div>
<div className="flex justify-between">
<span className="text-sm text-gray-500">Runtime</span>
<span className="text-sm font-medium">{form.runtime}</span>
<span className="text-sm font-medium">
{form.runtime}
{form.runtime === 'nodejs' && form.runtimeVersion ? ` v${form.runtimeVersion}` : ''}
{form.runtime === 'wordpress' && form.runtimeVersion ? ` v${form.runtimeVersion}` : ''}
{(form.runtime === 'laravel' || form.runtime === 'wordpress') && form.phpVersion ? ` — PHP ${form.phpVersion}` : ''}
</span>
</div>
<div className="flex justify-between">
<span className="text-sm text-gray-500">Database</span>
<span className="text-sm font-medium">{form.databaseType}</span>
<span className="text-sm font-medium">
{form.databaseType}
{form.databaseType !== 'none' && form.dbVersion ? ` v${form.dbVersion}` : ''}
</span>
</div>
{form.databaseType !== 'none' && (
<>