feat: WordPress migration support — upload existing site files
- Add two deployment modes for WordPress: Fresh Install vs Migrate Existing Site - Fresh Install: vanilla WordPress from official image (existing behavior) - Migrate: upload ZIP with wp-content/ (themes, plugins, uploads), wp-config.php, .htaccess - Custom entrypoint merges staged wp-content into PVC on first container run - Add init container for fresh WordPress builds (empty source context for Kaniko) - Increase upload limit to 200MB for WordPress sites - Add PHP upload limits (64MB) and memory config in WordPress Dockerfile - Update deploy page review step to show WordPress mode info - All UI in English
This commit is contained in:
@@ -51,7 +51,7 @@ export class ApplicationsController {
|
|||||||
@ApiOperation({ summary: 'Upload application code (zip file)' })
|
@ApiOperation({ summary: 'Upload application code (zip file)' })
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiConsumes('multipart/form-data')
|
||||||
@UseInterceptors(FileInterceptor('file', {
|
@UseInterceptors(FileInterceptor('file', {
|
||||||
limits: { fileSize: 100 * 1024 * 1024 }, // 100MB
|
limits: { fileSize: 200 * 1024 * 1024 }, // 200MB (WordPress sites can be large)
|
||||||
}))
|
}))
|
||||||
async uploadCode(
|
async uploadCode(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
|
|||||||
@@ -205,12 +205,23 @@ export class BuildService {
|
|||||||
{ name: 'workspace', mountPath: '/workspace' },
|
{ name: 'workspace', mountPath: '/workspace' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// If no uploaded code and no git, mount dockerfile directly
|
// If no uploaded code and no git, we need to prepare the workspace
|
||||||
if (!hasUploadedCode && !hasGitUrl) {
|
if (!hasUploadedCode && !hasGitUrl) {
|
||||||
kanikoVolumeMounts.push({
|
// For runtimes that don't need source (e.g. fresh WordPress),
|
||||||
name: 'dockerfile',
|
// add an init container that creates empty source dir + copies Dockerfile
|
||||||
mountPath: '/workspace/Dockerfile',
|
initContainers.push({
|
||||||
subPath: 'Dockerfile',
|
name: 'prepare-workspace',
|
||||||
|
image: 'alpine:3.19',
|
||||||
|
command: ['sh', '-c', `
|
||||||
|
mkdir -p /workspace-out/source &&
|
||||||
|
cp /dockerfile/Dockerfile /workspace-out/Dockerfile &&
|
||||||
|
echo ">>> Prepared empty workspace for fresh install" &&
|
||||||
|
ls -la /workspace-out/
|
||||||
|
`],
|
||||||
|
volumeMounts: [
|
||||||
|
{ name: 'workspace', mountPath: '/workspace-out' },
|
||||||
|
{ name: 'dockerfile', mountPath: '/dockerfile' },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +402,8 @@ CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
|
|||||||
private wordpressDockerfile(app: Application): string {
|
private wordpressDockerfile(app: Application): string {
|
||||||
const wpVersion = app.runtimeVersion || '6.7';
|
const wpVersion = app.runtimeVersion || '6.7';
|
||||||
const phpVersion = app.phpVersion || '8.3';
|
const phpVersion = app.phpVersion || '8.3';
|
||||||
|
const hasUploadedCode = !!app.codePath;
|
||||||
|
|
||||||
return `FROM wordpress:${wpVersion}-php${phpVersion}-apache
|
return `FROM wordpress:${wpVersion}-php${phpVersion}-apache
|
||||||
|
|
||||||
# Install additional PHP extensions commonly needed by WordPress
|
# Install additional PHP extensions commonly needed by WordPress
|
||||||
@@ -399,27 +412,51 @@ RUN docker-php-ext-install opcache
|
|||||||
# Enable Apache mod_rewrite for pretty permalinks
|
# Enable Apache mod_rewrite for pretty permalinks
|
||||||
RUN a2enmod rewrite
|
RUN a2enmod rewrite
|
||||||
|
|
||||||
# Copy user's custom themes, plugins, and uploads if provided
|
# Increase PHP upload limits for WordPress media
|
||||||
|
RUN echo "upload_max_filesize = 64M\\npost_max_size = 64M\\nmax_execution_time = 300\\nmemory_limit = 256M" > /usr/local/etc/php/conf.d/uploads.ini
|
||||||
|
|
||||||
|
${hasUploadedCode ? `# Copy user's custom WordPress files
|
||||||
COPY . /tmp/user-content
|
COPY . /tmp/user-content
|
||||||
|
|
||||||
# Merge user content into the WordPress installation
|
# Merge user content into a staging area for wp-content
|
||||||
# - wp-content/themes, wp-content/plugins, wp-content/uploads
|
# The actual wp-content is on a PVC, so we stage it and copy at runtime
|
||||||
# - Also support full WordPress roots (with wp-config.php, etc.)
|
RUN mkdir -p /usr/src/wordpress-user && \\
|
||||||
RUN if [ -d /tmp/user-content/wp-content ]; then \\
|
if [ -d /tmp/user-content/wp-content ]; then \\
|
||||||
cp -a /tmp/user-content/wp-content/. /var/www/html/wp-content/; \\
|
echo ">>> Staging user wp-content (themes, plugins, uploads)..." && \\
|
||||||
|
cp -a /tmp/user-content/wp-content /usr/src/wordpress-user/wp-content; \\
|
||||||
fi && \\
|
fi && \\
|
||||||
if [ -f /tmp/user-content/wp-config.php ]; then \\
|
if [ -f /tmp/user-content/wp-config.php ]; then \\
|
||||||
|
echo ">>> Copying custom wp-config.php" && \\
|
||||||
cp /tmp/user-content/wp-config.php /var/www/html/wp-config.php; \\
|
cp /tmp/user-content/wp-config.php /var/www/html/wp-config.php; \\
|
||||||
fi && \\
|
fi && \\
|
||||||
# Copy any loose PHP files (custom root files)
|
if [ -f /tmp/user-content/.htaccess ]; then \\
|
||||||
|
echo ">>> Copying .htaccess" && \\
|
||||||
|
cp /tmp/user-content/.htaccess /var/www/html/.htaccess; \\
|
||||||
|
fi && \\
|
||||||
find /tmp/user-content -maxdepth 1 -name "*.php" ! -name "wp-config.php" -exec cp {} /var/www/html/ \\\\; 2>/dev/null || true && \\
|
find /tmp/user-content -maxdepth 1 -name "*.php" ! -name "wp-config.php" -exec cp {} /var/www/html/ \\\\; 2>/dev/null || true && \\
|
||||||
rm -rf /tmp/user-content
|
rm -rf /tmp/user-content && \\
|
||||||
|
echo ">>> WordPress user content staged"
|
||||||
|
|
||||||
|
# Custom entrypoint: merge staged wp-content into PVC on first run, then run WP
|
||||||
|
RUN { \\
|
||||||
|
echo '#!/bin/bash'; \\
|
||||||
|
echo 'set -e'; \\
|
||||||
|
echo 'if [ -d /usr/src/wordpress-user/wp-content ] && [ ! -f /var/www/html/wp-content/.user-content-merged ]; then'; \\
|
||||||
|
echo ' echo ">>> First run: merging user wp-content into PVC..."'; \\
|
||||||
|
echo ' cp -a /usr/src/wordpress-user/wp-content/. /var/www/html/wp-content/'; \\
|
||||||
|
echo ' touch /var/www/html/wp-content/.user-content-merged'; \\
|
||||||
|
echo ' chown -R www-data:www-data /var/www/html/wp-content'; \\
|
||||||
|
echo ' echo ">>> User wp-content merged successfully"'; \\
|
||||||
|
echo 'fi'; \\
|
||||||
|
echo 'exec docker-entrypoint.sh apache2-foreground'; \\
|
||||||
|
} > /usr/local/bin/cloudhost-entrypoint.sh && chmod +x /usr/local/bin/cloudhost-entrypoint.sh
|
||||||
|
` : `# Fresh install — no user content to merge
|
||||||
|
`}
|
||||||
# Set proper ownership
|
# Set proper ownership
|
||||||
RUN chown -R www-data:www-data /var/www/html
|
RUN chown -R www-data:www-data /var/www/html
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
CMD ["apache2-foreground"]
|
CMD [${hasUploadedCode ? '"cloudhost-entrypoint.sh"' : '"apache2-foreground"'}]
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ export default function DeployPage() {
|
|||||||
const [dbDumpFile, setDbDumpFile] = useState<File | null>(null);
|
const [dbDumpFile, setDbDumpFile] = useState<File | null>(null);
|
||||||
const dbDumpInputRef = useRef<HTMLInputElement>(null);
|
const dbDumpInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [dbUploadProgress, setDbUploadProgress] = useState(0);
|
const [dbUploadProgress, setDbUploadProgress] = useState(0);
|
||||||
|
const [wpMode, setWpMode] = useState<'fresh' | 'migrate'>('fresh');
|
||||||
|
const [wpContentFile, setWpContentFile] = useState<File | null>(null);
|
||||||
|
const [isWpDragging, setIsWpDragging] = useState(false);
|
||||||
|
const wpFileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [selectedCycle, setSelectedCycle] = useState<BillingCycle>('monthly');
|
const [selectedCycle, setSelectedCycle] = useState<BillingCycle>('monthly');
|
||||||
const [paymentMethod, setPaymentMethod] = useState<'wallet' | 'gateway'>('wallet');
|
const [paymentMethod, setPaymentMethod] = useState<'wallet' | 'gateway'>('wallet');
|
||||||
const [isPaid, setIsPaid] = useState(false);
|
const [isPaid, setIsPaid] = useState(false);
|
||||||
@@ -99,10 +103,11 @@ export default function DeployPage() {
|
|||||||
const res = await api.post('/applications', payload);
|
const res = await api.post('/applications', payload);
|
||||||
const appId = res.data.id;
|
const appId = res.data.id;
|
||||||
|
|
||||||
// Upload source
|
// Upload source (regular apps or WordPress migrate)
|
||||||
if (sourceMethod === 'upload' && zipFile) {
|
const fileToUpload = form.runtime === 'wordpress' ? (wpMode === 'migrate' ? wpContentFile : null) : (sourceMethod === 'upload' ? zipFile : null);
|
||||||
|
if (fileToUpload) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', zipFile);
|
formData.append('file', fileToUpload);
|
||||||
await api.post(`/applications/${appId}/upload`, formData, {
|
await api.post(`/applications/${appId}/upload`, formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
onUploadProgress: (e) => { if (e.total) setUploadProgress(Math.round((e.loaded * 100) / e.total)); },
|
onUploadProgress: (e) => { if (e.total) setUploadProgress(Math.round((e.loaded * 100) / e.total)); },
|
||||||
@@ -159,10 +164,11 @@ export default function DeployPage() {
|
|||||||
const res = await api.post('/applications', payload);
|
const res = await api.post('/applications', payload);
|
||||||
const appId = res.data.id;
|
const appId = res.data.id;
|
||||||
|
|
||||||
// Upload source
|
// Upload source (regular apps or WordPress migrate)
|
||||||
if (sourceMethod === 'upload' && zipFile) {
|
const fileToUpload = form.runtime === 'wordpress' ? (wpMode === 'migrate' ? wpContentFile : null) : (sourceMethod === 'upload' ? zipFile : null);
|
||||||
|
if (fileToUpload) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', zipFile);
|
formData.append('file', fileToUpload);
|
||||||
await api.post(`/applications/${appId}/upload`, formData, {
|
await api.post(`/applications/${appId}/upload`, formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
onUploadProgress: (e) => { if (e.total) setUploadProgress(Math.round((e.loaded * 100) / e.total)); },
|
onUploadProgress: (e) => { if (e.total) setUploadProgress(Math.round((e.loaded * 100) / e.total)); },
|
||||||
@@ -201,9 +207,11 @@ export default function DeployPage() {
|
|||||||
const appId = res.data.id;
|
const appId = res.data.id;
|
||||||
|
|
||||||
// Upload zip file if selected
|
// Upload zip file if selected
|
||||||
if (sourceMethod === 'upload' && zipFile) {
|
// Upload source (regular apps or WordPress migrate)
|
||||||
|
const fileToUpload = data.runtime === 'wordpress' ? (wpMode === 'migrate' ? wpContentFile : null) : (sourceMethod === 'upload' ? zipFile : null);
|
||||||
|
if (fileToUpload) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', zipFile);
|
formData.append('file', fileToUpload);
|
||||||
await api.post(`/applications/${appId}/upload`, formData, {
|
await api.post(`/applications/${appId}/upload`, formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
onUploadProgress: (e) => {
|
onUploadProgress: (e) => {
|
||||||
@@ -294,11 +302,35 @@ export default function DeployPage() {
|
|||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleWpFileSelect = useCallback((file: File) => {
|
||||||
|
const validExtensions = ['.zip', '.tar.gz', '.tgz'];
|
||||||
|
const hasValidExt = validExtensions.some((ext) => file.name.toLowerCase().endsWith(ext));
|
||||||
|
if (!hasValidExt) {
|
||||||
|
toast.error('Only .zip or .tar.gz files are allowed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.size > 200 * 1024 * 1024) {
|
||||||
|
toast.error('WordPress files must be less than 200MB');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setWpContentFile(file);
|
||||||
|
toast.success(`WordPress files selected: ${file.name}`);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleWpDrop = useCallback((e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsWpDragging(false);
|
||||||
|
const file = e.dataTransfer.files[0];
|
||||||
|
if (file) handleWpFileSelect(file);
|
||||||
|
}, [handleWpFileSelect]);
|
||||||
|
|
||||||
const canNext = () => {
|
const canNext = () => {
|
||||||
if (step === 0) {
|
if (step === 0) {
|
||||||
if (form.name.length < 2) return false;
|
if (form.name.length < 2) return false;
|
||||||
// WordPress doesn't require source code
|
// WordPress: migrate mode requires wp-content file
|
||||||
if (form.runtime !== 'wordpress') {
|
if (form.runtime === 'wordpress') {
|
||||||
|
if (wpMode === 'migrate' && !wpContentFile) return false;
|
||||||
|
} else {
|
||||||
if (sourceMethod === 'upload' && !zipFile) return false;
|
if (sourceMethod === 'upload' && !zipFile) return false;
|
||||||
if (sourceMethod === 'git' && !form.gitUrl) return false;
|
if (sourceMethod === 'git' && !form.gitUrl) return false;
|
||||||
}
|
}
|
||||||
@@ -387,6 +419,8 @@ export default function DeployPage() {
|
|||||||
else if (opt.value === 'laravel') { updates.port = 8000; updates.runtimeVersion = ''; updates.phpVersion = '8.3'; }
|
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'; }
|
else if (opt.value === 'wordpress') { updates.port = 80; updates.databaseType = 'mysql'; updates.runtimeVersion = '6.7'; updates.phpVersion = '8.3'; }
|
||||||
setForm({ ...form, ...updates });
|
setForm({ ...form, ...updates });
|
||||||
|
// Reset WordPress-specific state when switching types
|
||||||
|
if (opt.value !== 'wordpress') { setWpMode('fresh'); setWpContentFile(null); }
|
||||||
}}
|
}}
|
||||||
className={`p-4 rounded-xl border-2 text-left transition-colors ${
|
className={`p-4 rounded-xl border-2 text-left transition-colors ${
|
||||||
form.runtime === opt.value ? 'border-primary-500 bg-primary-50' : 'border-gray-200 hover:border-gray-300'
|
form.runtime === opt.value ? 'border-primary-500 bg-primary-50' : 'border-gray-200 hover:border-gray-300'
|
||||||
@@ -534,17 +568,128 @@ export default function DeployPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* WordPress info — shown when WordPress is selected */}
|
{/* WordPress deployment mode — shown when WordPress is selected */}
|
||||||
{form.runtime === 'wordpress' && (
|
{form.runtime === 'wordpress' && (
|
||||||
<div className="p-4 bg-blue-50/50 border border-blue-200 rounded-xl">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">Deployment Mode</label>
|
||||||
<Hexagon className="w-5 h-5 text-blue-600" />
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<h3 className="text-sm font-semibold text-gray-800">WordPress (Official Image)</h3>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => { setWpMode('fresh'); setWpContentFile(null); }}
|
||||||
|
className={`p-4 rounded-xl border-2 text-left transition-colors ${
|
||||||
|
wpMode === 'fresh' ? 'border-primary-500 bg-primary-50' : 'border-gray-200 hover:border-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Rocket className="w-5 h-5 text-blue-600" />
|
||||||
|
<p className="mt-2 font-semibold text-sm text-gray-900">Fresh Install</p>
|
||||||
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
|
Start with a clean WordPress. Install themes & plugins via the admin panel.
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setWpMode('migrate')}
|
||||||
|
className={`p-4 rounded-xl border-2 text-left transition-colors ${
|
||||||
|
wpMode === 'migrate' ? 'border-primary-500 bg-primary-50' : 'border-gray-200 hover:border-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Upload className="w-5 h-5 text-emerald-600" />
|
||||||
|
<p className="mt-2 font-semibold text-sm text-gray-900">Migrate Existing Site</p>
|
||||||
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
|
Upload your WordPress files (wp-content, themes, plugins) and optionally a DB dump.
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500">
|
|
||||||
وردپرس از ایمیج رسمی Docker استفاده میکند و نیازی به آپلود سورسکد ندارد.
|
{wpMode === 'fresh' && (
|
||||||
قالبها و افزونهها از طریق پنل مدیریت وردپرس نصب میشوند.
|
<div className="p-4 bg-blue-50/50 border border-blue-200 rounded-xl">
|
||||||
</p>
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<Hexagon className="w-5 h-5 text-blue-600" />
|
||||||
|
<h3 className="text-sm font-semibold text-gray-800">WordPress (Official Image)</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
A fresh WordPress installation will be deployed using the official Docker image.
|
||||||
|
You can install themes and plugins via the WordPress admin panel after deployment.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{wpMode === 'migrate' && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="p-3 bg-amber-50 border border-amber-200 rounded-xl">
|
||||||
|
<p className="text-xs text-gray-600">
|
||||||
|
<strong>Upload a ZIP</strong> containing your WordPress files. Supported structures:
|
||||||
|
</p>
|
||||||
|
<ul className="text-xs text-gray-500 mt-1 ml-4 list-disc space-y-0.5">
|
||||||
|
<li><code className="bg-amber-100 px-1 rounded">wp-content/</code> — themes, plugins, uploads</li>
|
||||||
|
<li><code className="bg-amber-100 px-1 rounded">wp-config.php</code> — custom configuration (optional)</li>
|
||||||
|
<li>Any custom <code className="bg-amber-100 px-1 rounded">.php</code> files at root level</li>
|
||||||
|
</ul>
|
||||||
|
<p className="text-xs text-gray-500 mt-1.5">
|
||||||
|
You can also upload a SQL database dump in the next step to restore your data.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{wpContentFile ? (
|
||||||
|
<div className="flex items-center justify-between p-4 bg-green-50 border border-green-200 rounded-xl">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center text-green-600">
|
||||||
|
<CheckCircle className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-green-800">{wpContentFile.name}</p>
|
||||||
|
<p className="text-xs text-green-600">
|
||||||
|
{(wpContentFile.size / (1024 * 1024)).toFixed(2)} MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setWpContentFile(null);
|
||||||
|
if (wpFileInputRef.current) wpFileInputRef.current.value = '';
|
||||||
|
}}
|
||||||
|
className="text-sm text-red-500 hover:text-red-700 font-medium"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
onDrop={handleWpDrop}
|
||||||
|
onDragOver={(e) => { e.preventDefault(); setIsWpDragging(true); }}
|
||||||
|
onDragLeave={(e) => { e.preventDefault(); setIsWpDragging(false); }}
|
||||||
|
onClick={() => wpFileInputRef.current?.click()}
|
||||||
|
className={`border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-all ${
|
||||||
|
isWpDragging
|
||||||
|
? 'border-primary-500 bg-primary-50'
|
||||||
|
: 'border-gray-300 hover:border-primary-400 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<FolderUp className="w-8 h-8 mx-auto text-gray-400" />
|
||||||
|
<p className="text-sm font-medium text-gray-700">
|
||||||
|
Drag & drop your WordPress ZIP here
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
ZIP with <strong>wp-content/</strong> folder • Max 200MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
ref={wpFileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept=".zip,.tar.gz,.tgz"
|
||||||
|
className="hidden"
|
||||||
|
onChange={(e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) handleWpFileSelect(file);
|
||||||
|
e.target.value = '';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1179,7 +1324,9 @@ export default function DeployPage() {
|
|||||||
<span className="text-sm text-gray-500">Source</span>
|
<span className="text-sm text-gray-500">Source</span>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
{form.runtime === 'wordpress'
|
{form.runtime === 'wordpress'
|
||||||
? 'WordPress (Official Image)'
|
? wpMode === 'migrate' && wpContentFile
|
||||||
|
? `Migrate: ${wpContentFile.name} (${(wpContentFile.size / (1024 * 1024)).toFixed(1)} MB)`
|
||||||
|
: 'WordPress (Fresh Install)'
|
||||||
: sourceMethod === 'upload'
|
: sourceMethod === 'upload'
|
||||||
? zipFile
|
? zipFile
|
||||||
? zipFile.name
|
? zipFile.name
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user