diff --git a/src/components/TicketProjects.jsx b/src/components/TicketProjects.jsx index 9351f5f..e1c395e 100644 --- a/src/components/TicketProjects.jsx +++ b/src/components/TicketProjects.jsx @@ -135,6 +135,10 @@ export default function TicketProjects({ ticket }) { const create = (e) => { e.preventDefault() + if (!slugify(createForm.subdomain)) { + setError('Bitte eine gültige Subdomain eingeben (Buchstaben, Ziffern, Bindestrich).') + return + } run(async () => { const subdomain = slugify(createForm.subdomain) await createProjectFromTemplate({ diff --git a/src/lib/throttleRuns.js b/src/lib/throttleRuns.js index 70b03bd..537a716 100644 --- a/src/lib/throttleRuns.js +++ b/src/lib/throttleRuns.js @@ -1,5 +1,7 @@ // Ein Aufruf hoechstens alle `maxAgeMs`: ein laufender Aufruf wird geteilt, danach liefert die -// Funktion bis zum Ablauf null (uebersprungen). `now` nur fuer Tests. +// Funktion bis zum Ablauf null (uebersprungen). Nach einem Fehler darf sofort neu versucht +// werden (sonst verschwand der Hinweis und der Abgleich fiel eine Minute lang still aus). +// `now` nur fuer Tests. export function throttleRuns(run, maxAgeMs) { let pending = null let doneAt = 0 @@ -8,10 +10,10 @@ export function throttleRuns(run, maxAgeMs) { if (doneAt && now - doneAt < maxAgeMs) return Promise.resolve(null) pending = Promise.resolve() .then(run) - .finally(() => { - doneAt = Date.now() - pending = null - }) + .then( + (result) => { doneAt = Date.now(); pending = null; return result }, + (err) => { doneAt = 0; pending = null; throw err } + ) return pending } } diff --git a/src/pages/AssignProjectPage.jsx b/src/pages/AssignProjectPage.jsx index 9cf9f83..3731c9e 100644 --- a/src/pages/AssignProjectPage.jsx +++ b/src/pages/AssignProjectPage.jsx @@ -357,7 +357,9 @@ export default function AssignProjectPage() { {isWebsiteWithCustomer && hasPreviewAlready && (
- Preview läuft bereits unter https://{project.subdomain}.project.webklar.com und bleibt unverändert. + {['deployed', 'ready'].includes(String(project.status || '').toLowerCase()) + ? <>Preview läuft bereits unter https://{project.subdomain}.project.webklar.com und bleibt unverändert.> + : <>Preview ist unter https://{project.subdomain}.project.webklar.com eingerichtet (Status: {project.status || 'unbekannt'}). Neu bauen geht im Projekt über „Preview".>}
)} {isWebsiteWithCustomer && !hasPreviewAlready && ( diff --git a/src/pages/ProjectsPage.jsx b/src/pages/ProjectsPage.jsx index 011a757..19acdd6 100644 --- a/src/pages/ProjectsPage.jsx +++ b/src/pages/ProjectsPage.jsx @@ -24,6 +24,7 @@ import { updateWebsiteProject, archiveWebsiteProject, unarchiveWebsiteProject, + importNewReposThrottled, } from '../lib/projectAdminApi' import { assignSummary, ticketIdForCustomer } from '../lib/projectAssign' import { projectCounts, projectsInView } from '../lib/projectList' @@ -116,8 +117,15 @@ export default function ProjectsPage() { const [editError, setEditError] = useState('') const [archiveBusy, setArchiveBusy] = useState(false) + // Erst die bekannte Liste zeigen, dann neue/archivierte Gitea-Repos abholen und neu laden - + // sonst fehlte ein frisch angelegtes Repo hier bis zum naechsten 10-Minuten-Lauf. useEffect(() => { + let cancelled = false fetchAllProjects() + importNewReposThrottled() + .then((r) => { if (r && !cancelled) fetchAllProjects() }) + .catch(() => { /* Import-Fehler zeigt die Ticketkarte; die Liste bleibt wie sie ist */ }) + return () => { cancelled = true } }, [fetchAllProjects]) const customerMap = useMemo(() => { diff --git a/tests/unit/importThrottle.test.mjs b/tests/unit/importThrottle.test.mjs index 70f25bf..6b1cff4 100644 --- a/tests/unit/importThrottle.test.mjs +++ b/tests/unit/importThrottle.test.mjs @@ -24,8 +24,11 @@ test('Repo-Import hoechstens einmal pro Minute, laufender Abgleich wird geteilt' assert.equal(runs, 2) }) -test('Fehler wird weitergereicht, der naechste Versuch wartet trotzdem die Minute ab', async () => { - const throttled = throttleRuns(async () => { throw new Error('weg') }, 60000) +test('Fehler wird weitergereicht, danach darf sofort neu versucht werden', async () => { + let runs = 0 + const throttled = throttleRuns(async () => { runs += 1; if (runs === 1) throw new Error('weg'); return { ok: true } }, 60000) await assert.rejects(throttled(), /weg/) + assert.deepEqual(await throttled(), { ok: true }) + assert.equal(runs, 2) assert.equal(await throttled(), null) })