feat(deployments): repo tabanlı kurulum sistemi ekle ve root taramayı kaldır
Root dizin taraması yerine repo URL tabanlı otomatik kurulum sistemine geçiş yapıldı.
Deploy klasörü artık repo URL'sinden otomatik oluşturuluyor. Remote repo
üzerinden branch ve compose dosyası listelemesi eklendi.
- `deploymentsRoot` konfigürasyonu kaldırıldı
- `/deployments/scan` endpoint'i kaldırıldı
- `/deployments/compose-files` endpoint'i eklendi
- `repoUrl` alanı unique ve index olarak işaretlendi
- Proje oluştururken `rootPath` zorunluluğu kaldırıldı
- Deploy klasörü otomatik `deployments/{slug}` formatında oluşturuluyor
- Frontend'de root tarama UI'ı kaldırıldı, compose dosyası listeleme eklendi
BREAKING CHANGE: Root dizin tarama özelliği ve `rootPath` alanı kaldırıldı.
Artık deploymentlar sadece repo URL ile oluşturulabiliyor.
This commit is contained in:
@@ -16,13 +16,12 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
|
||||
import {
|
||||
createDeployment,
|
||||
deleteDeployment,
|
||||
DeploymentCandidate,
|
||||
DeploymentInput,
|
||||
DeploymentProject,
|
||||
fetchDeploymentComposeFiles,
|
||||
fetchDeploymentBranches,
|
||||
fetchDeployments,
|
||||
runDeployment,
|
||||
scanDeployments,
|
||||
updateDeployment
|
||||
} from "../api/deployments";
|
||||
import { JobStatusBadge } from "../components/JobStatusBadge";
|
||||
@@ -30,7 +29,6 @@ import { JobStatusBadge } from "../components/JobStatusBadge";
|
||||
type FormState = {
|
||||
_id?: string;
|
||||
name: string;
|
||||
rootPath: string;
|
||||
repoUrl: string;
|
||||
branch: string;
|
||||
composeFile: DeploymentInput["composeFile"];
|
||||
@@ -39,7 +37,6 @@ type FormState = {
|
||||
|
||||
const defaultForm: FormState = {
|
||||
name: "",
|
||||
rootPath: "",
|
||||
repoUrl: "",
|
||||
branch: "main",
|
||||
composeFile: "docker-compose.yml",
|
||||
@@ -54,19 +51,15 @@ export function DeploymentsPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [scanning, setScanning] = useState(false);
|
||||
const [candidates, setCandidates] = useState<DeploymentCandidate[]>([]);
|
||||
const [form, setForm] = useState<FormState>(defaultForm);
|
||||
const [pendingEditId, setPendingEditId] = useState<string | null>(null);
|
||||
const [branchOptions, setBranchOptions] = useState<string[]>([]);
|
||||
const [branchLoading, setBranchLoading] = useState(false);
|
||||
const [composeOptions, setComposeOptions] = useState<DeploymentInput["composeFile"][]>([]);
|
||||
const [composeLoading, setComposeLoading] = useState(false);
|
||||
const [faviconErrors, setFaviconErrors] = useState<Record<string, boolean>>({});
|
||||
|
||||
const isEdit = useMemo(() => !!form._id, [form._id]);
|
||||
const selectedCandidate = useMemo(
|
||||
() => candidates.find((c) => c.rootPath === form.rootPath),
|
||||
[candidates, form.rootPath]
|
||||
);
|
||||
|
||||
const loadDeployments = async () => {
|
||||
setLoading(true);
|
||||
@@ -80,18 +73,6 @@ export function DeploymentsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const loadCandidates = async () => {
|
||||
setScanning(true);
|
||||
try {
|
||||
const data = await scanDeployments();
|
||||
setCandidates(data);
|
||||
} catch {
|
||||
toast.error("Root taraması yapılamadı");
|
||||
} finally {
|
||||
setScanning(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadDeployments();
|
||||
}, []);
|
||||
@@ -100,6 +81,7 @@ export function DeploymentsPage() {
|
||||
const repoUrl = form.repoUrl.trim();
|
||||
if (!repoUrl) {
|
||||
setBranchOptions([]);
|
||||
setComposeOptions([]);
|
||||
return;
|
||||
}
|
||||
const timer = setTimeout(async () => {
|
||||
@@ -119,6 +101,30 @@ export function DeploymentsPage() {
|
||||
return () => clearTimeout(timer);
|
||||
}, [form.repoUrl, form.branch]);
|
||||
|
||||
useEffect(() => {
|
||||
const repoUrl = form.repoUrl.trim();
|
||||
const branch = form.branch.trim();
|
||||
if (!repoUrl || !branch) {
|
||||
setComposeOptions([]);
|
||||
return;
|
||||
}
|
||||
const timer = setTimeout(async () => {
|
||||
setComposeLoading(true);
|
||||
try {
|
||||
const files = await fetchDeploymentComposeFiles(repoUrl, branch);
|
||||
setComposeOptions(files);
|
||||
if (files.length > 0 && !files.includes(form.composeFile)) {
|
||||
setForm((prev) => ({ ...prev, composeFile: files[0] }));
|
||||
}
|
||||
} catch {
|
||||
setComposeOptions([]);
|
||||
} finally {
|
||||
setComposeLoading(false);
|
||||
}
|
||||
}, 400);
|
||||
return () => clearTimeout(timer);
|
||||
}, [form.repoUrl, form.branch, form.composeFile]);
|
||||
|
||||
useEffect(() => {
|
||||
const state = location.state as { editDeploymentId?: string } | null;
|
||||
if (state?.editDeploymentId) {
|
||||
@@ -139,16 +145,15 @@ export function DeploymentsPage() {
|
||||
const handleOpenNew = async () => {
|
||||
setForm(defaultForm);
|
||||
setBranchOptions([]);
|
||||
setComposeOptions([]);
|
||||
setModalOpen(true);
|
||||
await loadCandidates();
|
||||
};
|
||||
|
||||
const handleEdit = (deployment: DeploymentProject) => {
|
||||
const { _id, name, rootPath, repoUrl, branch, composeFile, port } = deployment;
|
||||
const { _id, name, repoUrl, branch, composeFile, port } = deployment;
|
||||
setForm({
|
||||
_id,
|
||||
name,
|
||||
rootPath,
|
||||
repoUrl,
|
||||
branch,
|
||||
composeFile,
|
||||
@@ -166,14 +171,13 @@ export function DeploymentsPage() {
|
||||
try {
|
||||
const payload: DeploymentInput = {
|
||||
name: form.name,
|
||||
rootPath: form.rootPath,
|
||||
repoUrl: form.repoUrl,
|
||||
branch: form.branch,
|
||||
composeFile: form.composeFile,
|
||||
port: form.port ? Number(form.port) : undefined
|
||||
};
|
||||
|
||||
if (!payload.name || !payload.rootPath || !payload.repoUrl || !payload.branch || !payload.composeFile) {
|
||||
if (!payload.name || !payload.repoUrl || !payload.branch || !payload.composeFile) {
|
||||
toast.error("Tüm alanları doldurun");
|
||||
setSaving(false);
|
||||
return;
|
||||
@@ -374,48 +378,8 @@ export function DeploymentsPage() {
|
||||
|
||||
<div className="max-h-[70vh] space-y-4 overflow-y-auto px-5 py-4">
|
||||
{!isEdit && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>Proje Klasörü</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={loadCandidates}
|
||||
disabled={scanning}
|
||||
>
|
||||
{scanning ? "Taranıyor..." : "Yeniden Tara"}
|
||||
</Button>
|
||||
</div>
|
||||
<Select
|
||||
value={form.rootPath}
|
||||
onValueChange={(value) => {
|
||||
const candidate = candidates.find((c) => c.rootPath === value);
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
rootPath: value,
|
||||
name: candidate?.name || prev.name,
|
||||
composeFile: candidate?.composeFiles[0] || prev.composeFile
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Root altında proje seçin" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{candidates.map((candidate) => (
|
||||
<SelectItem key={candidate.rootPath} value={candidate.rootPath}>
|
||||
{candidate.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{scanning
|
||||
? "Root dizin taranıyor..."
|
||||
: candidates.length === 0
|
||||
? "Root altında compose dosyası bulunan proje yok."
|
||||
: "Compose dosyası bulunan klasörleri listeler."}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Repo URL girildiğinde branch ve compose dosyaları listelenir.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -491,15 +455,23 @@ export function DeploymentsPage() {
|
||||
<SelectValue placeholder="Compose seçin" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(selectedCandidate?.composeFiles || ["docker-compose.yml", "docker-compose.dev.yml"]).map(
|
||||
(file) => (
|
||||
<SelectItem key={file} value={file}>
|
||||
{file}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
{(composeOptions.length > 0
|
||||
? composeOptions
|
||||
: ["docker-compose.yml", "docker-compose.dev.yml"]
|
||||
).map((file) => (
|
||||
<SelectItem key={file} value={file}>
|
||||
{file}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{composeLoading
|
||||
? "Compose dosyaları alınıyor..."
|
||||
: composeOptions.length > 0
|
||||
? "Repo üzerindeki compose dosyaları listelendi."
|
||||
: "Repo URL ve branch sonrası compose dosyaları listelenir."}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user