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)
+})