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:
Z User committed 2026-10-29 16:29:28 +00:00
1 parent 32a8b01845
commit 793bb8c366
3 files changed
+284 -39

No files matched your search

+69 -38
View File
@@ -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>