diff --git a/src/components/AssignedProjectCard.jsx b/src/components/AssignedProjectCard.jsx index 1e7daa9..b68023a 100644 --- a/src/components/AssignedProjectCard.jsx +++ b/src/components/AssignedProjectCard.jsx @@ -112,7 +112,7 @@ export default function AssignedProjectCard({ project, onUnassign, showActions = - {showActions && ( + {showActions && project.subdomain && (
diff --git a/src/components/PreviewBlock.jsx b/src/components/PreviewBlock.jsx index 44db794..f3a7f9f 100644 --- a/src/components/PreviewBlock.jsx +++ b/src/components/PreviewBlock.jsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { MODE_OPTIONS, previewStatus, modeHint } from '../lib/previewView' -import { isValidEnvName, buildEnvPayload, isDevPreview } from '../lib/previewEnv' +import { isValidEnvName, buildEnvPayload, isDevPreview, dotColor, envValueTooLong, MAX_ENV_VARS } from '../lib/previewEnv' import { fetchPreview, setPreviewMode, @@ -11,7 +11,7 @@ import { fetchPreviewLog, } from '../lib/previewApi' -const DOT = { green: '#10b981', yellow: '#f59e0b', red: '#ef4444', grey: '#a0aec0' } +const STATE_OF_COLOR = { green: 'online', yellow: 'building', red: 'failed', grey: 'off' } const NOT_ACTIVE = 'Preview-Runner ist nicht aktiv' const logDate = (at) => { @@ -59,6 +59,7 @@ export default function PreviewBlock({ projectId }) { if (!alive.current) return setData(r) setInactive(false) + setError('') } catch (err) { handleError(err) } finally { @@ -93,10 +94,17 @@ export default function PreviewBlock({ projectId }) { setLogFile(runFile) setLogText('') if (!runFile) return - run(async () => { - const r = await fetchPreviewLog(projectId, runFile) - setLogText(Array.isArray(r.lines) ? r.lines.join('\n') : String(r.log ?? r.text ?? '')) - }) + setBusy(true) + setError('') + fetchPreviewLog(projectId, runFile) + .then((r) => { + if (alive.current) setLogText(Array.isArray(r.lines) ? r.lines.join('\n') : String(r.log ?? r.text ?? '')) + }) + .catch((err) => { + handleError(err) + if (alive.current) setLogText(`Fehler: ${err?.message || 'Protokoll konnte nicht geladen werden'}`) + }) + .finally(() => { if (alive.current) setBusy(false) }) } const addPending = (name, value) => { @@ -113,6 +121,14 @@ export default function PreviewBlock({ projectId }) { setEnvError('Bitte einen Wert eingeben.') return } + if (envValueTooLong(newValue)) { + setEnvError('Der Wert ist zu lang (max. 4096 Byte).') + return + } + if (!names.includes(newName) && names.length >= MAX_ENV_VARS) { + setEnvError(`Maximal ${MAX_ENV_VARS} Variablen möglich.`) + return + } setEnvError('') addPending(newName, newValue) setNewName('') @@ -132,6 +148,10 @@ export default function PreviewBlock({ projectId }) { setEnvError('Bitte einen neuen Wert eingeben.') return } + if (envValueTooLong(editValue)) { + setEnvError('Der Wert ist zu lang (max. 4096 Byte).') + return + } setEnvError('') addPending(editName, editValue) setEditName('') @@ -144,6 +164,13 @@ export default function PreviewBlock({ projectId }) { setPendingRemove([]) }) + const enter = (fn) => (e) => { + if (e.key === 'Enter') { + e.preventDefault() + fn() + } + } + if (loading) return
Lädt …
const view = data ? { ...data } : { state: 'off' } @@ -153,7 +180,7 @@ export default function PreviewBlock({ projectId }) { const names = [...new Set([...(data?.envNames || []), ...Object.keys(pendingSet)])].filter((n) => !pendingRemove.includes(n)) const dirty = Object.keys(pendingSet).length > 0 || pendingRemove.length > 0 const dev = isDevPreview(data) - const logs = (data?.logs || []).slice(0, 5) + const logs = [...(data?.logs || [])].sort((a, b) => new Date(b.at) - new Date(a.at)).slice(0, 5) const small = { fontSize: 12 } @@ -176,7 +203,7 @@ export default function PreviewBlock({ projectId }) { {hint &&
{hint}
}
- + {status.text}
{data.state === 'failed' && data.error && ( @@ -240,7 +267,7 @@ export default function PreviewBlock({ projectId }) { ••• {editName === n ? ( <> - setEditValue(e.target.value)} /> + setEditValue(e.target.value)} /> ) : ( @@ -250,8 +277,8 @@ export default function PreviewBlock({ projectId }) { ))}
- setNewName(e.target.value)} /> - setNewValue(e.target.value)} /> + setNewName(e.target.value)} /> + setNewValue(e.target.value)} />
{envError &&
{envError}
} diff --git a/src/lib/previewEnv.js b/src/lib/previewEnv.js index 11d8a02..8390780 100644 --- a/src/lib/previewEnv.js +++ b/src/lib/previewEnv.js @@ -22,3 +22,8 @@ export function isDevPreview(p) { if (p.mode === 'dev') return true return (p.mode || 'auto') === 'auto' && typeof p.detected === 'string' && p.detected.startsWith('dev:') } + +export const MAX_ENV_VARS = 30 +export function envValueTooLong(value) { + return new TextEncoder().encode(String(value ?? '')).length > 4096 +} diff --git a/src/pages/ProjectsPage.jsx b/src/pages/ProjectsPage.jsx index 01e5b86..99ee8d2 100644 --- a/src/pages/ProjectsPage.jsx +++ b/src/pages/ProjectsPage.jsx @@ -735,13 +735,6 @@ export default function ProjectsPage() { )} - {editForm.projectType !== 'project' && ( -
- - -
- )} -
+ {editForm.projectType !== 'project' && ( +
+ + +
+ )} )} diff --git a/src/pages/ServicesPage.jsx b/src/pages/ServicesPage.jsx index c92ec47..4beb2d5 100644 --- a/src/pages/ServicesPage.jsx +++ b/src/pages/ServicesPage.jsx @@ -58,7 +58,9 @@ export default function ServicesPage() { + > + {`Preview: ${p.previewState}`} + )} {p.projectName || p.repoFullName} diff --git a/tests/unit/previewEnv.test.mjs b/tests/unit/previewEnv.test.mjs index 04ffdbc..50448dc 100644 --- a/tests/unit/previewEnv.test.mjs +++ b/tests/unit/previewEnv.test.mjs @@ -2,7 +2,7 @@ import test from 'node:test' import assert from 'node:assert/strict' import { loadModule } from './load.mjs' -const { isValidEnvName, buildEnvPayload, dotColor, isDevPreview } = await loadModule('src/lib/previewEnv.js') +const { isValidEnvName, buildEnvPayload, dotColor, isDevPreview, envValueTooLong, MAX_ENV_VARS } = await loadModule('src/lib/previewEnv.js') test('isValidEnvName', () => { assert.equal(isValidEnvName('VITE_API_URL'), true) @@ -35,3 +35,10 @@ test('isDevPreview', () => { assert.equal(isDevPreview({ mode: 'build', detected: 'dev:npm' }), false) assert.equal(isDevPreview(null), false) }) + +test('envValueTooLong zählt Bytes', () => { + assert.equal(envValueTooLong('a'.repeat(4096)), false) + assert.equal(envValueTooLong('a'.repeat(4097)), true) + assert.equal(envValueTooLong('ä'.repeat(2049)), true) + assert.equal(MAX_ENV_VARS, 30) +})