feat(import): add multi-site import modal and upload-multi API

This commit is contained in:
GitHub Actions
2025-11-30 00:35:34 +00:00
parent eb60530cec
commit 2014ff9fce
3 changed files with 109 additions and 0 deletions

View File

@@ -40,6 +40,11 @@ export const uploadCaddyfile = async (content: string): Promise<ImportPreview> =
return data;
};
export const uploadCaddyfilesMulti = async (contents: string[]): Promise<ImportPreview> => {
const { data } = await client.post<ImportPreview>('/import/upload-multi', { contents });
return data;
};
export const getImportPreview = async (): Promise<ImportPreview> => {
const { data } = await client.get<ImportPreview>('/import/preview');
return data;

View File

@@ -0,0 +1,90 @@
import React, { useState } from 'react'
import { uploadCaddyfilesMulti } from '../api/import'
type Props = {
visible: boolean
onClose: () => void
onUploaded?: () => void
}
export default function ImportSitesModal({ visible, onClose, onUploaded }: Props) {
const [sites, setSites] = useState<string[]>([''])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
if (!visible) return null
const setSite = (index: number, value: string) => {
const s = [...sites]
s[index] = value
setSites(s)
}
const addSite = () => setSites(prev => [...prev, ''])
const removeSite = (index: number) => setSites(prev => prev.filter((_, i) => i !== index))
const handleSubmit = async () => {
setError(null)
setLoading(true)
try {
const cleaned = sites.map(s => s || '')
await uploadCaddyfilesMulti(cleaned)
setLoading(false)
onUploaded && onUploaded()
onClose()
} catch (err: any) {
setError(err?.message || 'Upload failed')
setLoading(false)
}
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
<div className="relative bg-dark-card rounded-lg p-6 w-[900px] max-w-full">
<h3 className="text-xl font-semibold text-white mb-4">Multi-site Import</h3>
<p className="text-gray-400 text-sm mb-4">Add each site's Caddyfile content separately, then parse them together.</p>
<div className="space-y-4 max-h-[60vh] overflow-auto mb-4">
{sites.map((s, idx) => (
<div key={idx} className="border border-gray-800 rounded-lg p-3">
<div className="flex justify-between items-center mb-2">
<div className="text-sm text-gray-300">Site {idx + 1}</div>
<div>
{sites.length > 1 && (
<button
onClick={() => removeSite(idx)}
className="text-red-400 text-sm hover:underline mr-2"
>
Remove
</button>
)}
</div>
</div>
<textarea
value={s}
onChange={e => setSite(idx, e.target.value)}
placeholder={`example.com {\n reverse_proxy localhost:8080\n}`}
className="w-full h-48 bg-gray-900 border border-gray-700 rounded-lg p-3 text-white font-mono text-sm"
/>
</div>
))}
</div>
{error && <div className="bg-red-900/20 border border-red-500 text-red-400 px-4 py-2 rounded mb-4">{error}</div>}
<div className="flex gap-3 justify-end">
<button onClick={addSite} className="px-4 py-2 bg-gray-800 text-white rounded">+ Add site</button>
<button onClick={onClose} className="px-4 py-2 bg-gray-700 text-white rounded">Cancel</button>
<button
onClick={handleSubmit}
disabled={loading}
className="px-4 py-2 bg-blue-active text-white rounded disabled:opacity-60"
>
{loading ? 'Processing...' : 'Parse and Review'}
</button>
</div>
</div>
</div>
)
}

View File

@@ -2,11 +2,13 @@ import { useState } from 'react'
import { useImport } from '../hooks/useImport'
import ImportBanner from '../components/ImportBanner'
import ImportReviewTable from '../components/ImportReviewTable'
import ImportSitesModal from '../components/ImportSitesModal'
export default function ImportCaddy() {
const { session, preview, loading, error, upload, commit, cancel } = useImport()
const [content, setContent] = useState('')
const [showReview, setShowReview] = useState(false)
const [showMultiModal, setShowMultiModal] = useState(false)
const handleUpload = async () => {
if (!content.trim()) {
@@ -138,6 +140,12 @@ api.example.com {
>
{loading ? 'Processing...' : 'Parse and Review'}
</button>
<button
onClick={() => setShowMultiModal(true)}
className="ml-4 px-4 py-2 bg-gray-800 text-white rounded-lg"
>
Multi-site Import
</button>
</div>
</div>
)}
@@ -153,6 +161,12 @@ api.example.com {
onCancel={() => setShowReview(false)}
/>
)}
<ImportSitesModal
visible={showMultiModal}
onClose={() => setShowMultiModal(false)}
onUploaded={() => setShowReview(true)}
/>
</div>
)
}