feat: instance dialog uses AllowedRepo selector (replaces gitRepo/gitBranch inputs)
- Create Instance dialog now fetches repos from /api/aos/repos?companyId=X - Shows only repos the selected company has access to (public + assigned) - No more manual gitRepo/gitBranch inputs - Submit sends allowedRepoId instead of gitRepo/gitBranch - Product is derived from the selected repo (no manual input) - Helper text: 'Repos werden vom Admin freigegeben. Quelle: Gitea, Branch: main.'
This commit is contained in:
1 parent
32a8b01845
commit
793bb8c366
3 files changed
+284
-39
No files matched your search
@@ -175,6 +175,17 @@ interface InstanceStats {
|
||||
expiringSoon: number
|
||||
}
|
||||
|
||||
interface AllowedRepoLite {
|
||||
id: string
|
||||
giteaOwner: string
|
||||
giteaRepo: string
|
||||
displayName: string
|
||||
description: string | null
|
||||
product: string
|
||||
isPublic: boolean
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
interface InstancesData {
|
||||
instances: Instance[]
|
||||
stats: InstanceStats
|
||||
@@ -1183,10 +1194,8 @@ function CreateInstanceDialog({
|
||||
|
||||
const [companyId, setCompanyId] = useState('')
|
||||
const [licenseId, setLicenseId] = useState('')
|
||||
const [product, setProduct] = useState('restov1')
|
||||
const [allowedRepoId, setAllowedRepoId] = useState('')
|
||||
const [plan, setPlan] = useState<'starter' | 'pro' | 'enterprise'>('pro')
|
||||
const [gitRepo, setGitRepo] = useState('https://github.com/cetinmustafa83/restov1')
|
||||
const [gitBranch, setGitBranch] = useState('main')
|
||||
const [isDemo, setIsDemo] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
@@ -1198,6 +1207,11 @@ function CreateInstanceDialog({
|
||||
[crmData?.companies]
|
||||
)
|
||||
|
||||
// Fetch repos available to the selected company
|
||||
const { data: reposData } = useFetch<{ repos: AllowedRepoLite[] }>(
|
||||
companyId ? `/api/aos/repos?companyId=${companyId}` : null
|
||||
)
|
||||
|
||||
// Filter licenses: same company, no existing live instance.
|
||||
const availableLicenses = useMemo(() => {
|
||||
const all = licensesData?.licenses ?? []
|
||||
@@ -1220,22 +1234,28 @@ function CreateInstanceDialog({
|
||||
if (availableLicenses.length === 0) setLicenseId('')
|
||||
}, [availableLicenses, licenseId])
|
||||
|
||||
// Auto-select first available repo
|
||||
useEffect(() => {
|
||||
if (reposData?.repos?.[0] && !reposData.repos.find((r) => r.id === allowedRepoId)) {
|
||||
setAllowedRepoId(reposData.repos[0].id)
|
||||
}
|
||||
if (!reposData?.repos?.length) setAllowedRepoId('')
|
||||
}, [reposData, allowedRepoId])
|
||||
|
||||
// Reset form when closing
|
||||
useEffect(() => {
|
||||
if (open) return
|
||||
setCompanyId('')
|
||||
setLicenseId('')
|
||||
setProduct('restov1')
|
||||
setAllowedRepoId('')
|
||||
setPlan('pro')
|
||||
setGitRepo('https://github.com/cetinmustafa83/restov1')
|
||||
setGitBranch('main')
|
||||
setIsDemo(true)
|
||||
}, [open])
|
||||
|
||||
const submit = async () => {
|
||||
if (!companyId) { toast.error('Bitte Firma auswählen'); return }
|
||||
if (!licenseId) { toast.error('Bitte Lizenz auswählen'); return }
|
||||
if (!product.trim()) { toast.error('Produkt eingeben'); return }
|
||||
if (!allowedRepoId) { toast.error('Bitte Produkt/Repo auswählen'); return }
|
||||
setSaving(true)
|
||||
const t = toast.loading('Instanz wird provisioniert (1–3 Min)…')
|
||||
try {
|
||||
@@ -1245,10 +1265,8 @@ function CreateInstanceDialog({
|
||||
body: JSON.stringify({
|
||||
companyId,
|
||||
licenseId,
|
||||
product: product.trim(),
|
||||
allowedRepoId,
|
||||
plan,
|
||||
gitRepo: gitRepo.trim(),
|
||||
gitBranch: gitBranch.trim(),
|
||||
isDemo,
|
||||
}),
|
||||
})
|
||||
@@ -1267,6 +1285,8 @@ function CreateInstanceDialog({
|
||||
}
|
||||
}
|
||||
|
||||
const availableRepos = reposData?.repos ?? []
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
@@ -1322,35 +1342,46 @@ function CreateInstanceDialog({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Produkt + Plan */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Produkt</Label>
|
||||
<Input value={product} onChange={(e) => setProduct(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Plan</Label>
|
||||
<Select value={plan} onValueChange={(v) => setPlan(v as 'starter' | 'pro' | 'enterprise')}>
|
||||
<SelectTrigger className="w-full"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="starter">Starter</SelectItem>
|
||||
<SelectItem value="pro">Pro</SelectItem>
|
||||
<SelectItem value="enterprise">Enterprise</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{/* Produkt/Repo (from AllowedRepo list) */}
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Produkt / Repository</Label>
|
||||
<Select value={allowedRepoId} onValueChange={setAllowedRepoId} disabled={!companyId}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
!companyId
|
||||
? 'Zuerst Firma wählen'
|
||||
: availableRepos.length === 0
|
||||
? 'Keine Repos verfügbar'
|
||||
: 'Produkt wählen'
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableRepos.map((r) => (
|
||||
<SelectItem key={r.id} value={r.id}>
|
||||
{r.displayName}
|
||||
{r.description ? ` — ${r.description.slice(0, 50)}` : ''}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
Repos werden vom Admin freigegeben. Quelle: Gitea (git.dfmpanel.eu), Branch: main.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Git */}
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Git Repo</Label>
|
||||
<Input value={gitRepo} onChange={(e) => setGitRepo(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Git Branch</Label>
|
||||
<Input value={gitBranch} onChange={(e) => setGitBranch(e.target.value)} />
|
||||
</div>
|
||||
{/* Plan */}
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Plan</Label>
|
||||
<Select value={plan} onValueChange={(v) => setPlan(v as 'starter' | 'pro' | 'enterprise')}>
|
||||
<SelectTrigger className="w-full"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="starter">Starter</SelectItem>
|
||||
<SelectItem value="pro">Pro</SelectItem>
|
||||
<SelectItem value="enterprise">Enterprise</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Demo Switch */}
|
||||
@@ -1369,7 +1400,7 @@ function CreateInstanceDialog({
|
||||
<Button variant="ghost" onClick={() => onOpenChange(false)} disabled={saving}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button onClick={submit} disabled={saving || !licenseId}>
|
||||
<Button onClick={submit} disabled={saving || !licenseId || !allowedRepoId}>
|
||||
{saving ? <Loader2 className="size-4 animate-spin" /> : <Plus className="size-4" />}
|
||||
Provisionieren
|
||||
</Button>
|
||||
|
||||
Reference in new issue
Block a user