Unmapped ohne Kreuzjoin, Listen-GET kurz, Rebuild im Hintergrund. Tandoor-Zugang (URL/Token) in den Einstellungen mit ausliefern. Co-authored-by: Cursor <cursoragent@cursor.com>
141 lines
4.7 KiB
JavaScript
141 lines
4.7 KiB
JavaScript
import { useEffect, useState } from 'react'
|
|
import { api } from '../utils/api'
|
|
|
|
export default function TandoorSettings() {
|
|
const [baseUrl, setBaseUrl] = useState('')
|
|
const [token, setToken] = useState('')
|
|
const [tokenSet, setTokenSet] = useState(false)
|
|
const [tokenHint, setTokenHint] = useState(null)
|
|
const [lastOk, setLastOk] = useState(null)
|
|
const [lastError, setLastError] = useState(null)
|
|
const [busy, setBusy] = useState(false)
|
|
const [msg, setMsg] = useState(null)
|
|
|
|
const load = async () => {
|
|
const s = await api.getTandoorSettings()
|
|
setBaseUrl(s.base_url || '')
|
|
setTokenSet(Boolean(s.token_set))
|
|
setTokenHint(s.token_hint || null)
|
|
setLastOk(s.last_ok_at || null)
|
|
setLastError(s.last_error || null)
|
|
setToken('')
|
|
}
|
|
|
|
useEffect(() => {
|
|
load().catch((e) => setMsg({ type: 'err', text: e.message }))
|
|
}, [])
|
|
|
|
const save = async () => {
|
|
setBusy(true)
|
|
setMsg(null)
|
|
try {
|
|
const s = await api.putTandoorSettings({
|
|
base_url: baseUrl,
|
|
token: token.trim() || undefined,
|
|
})
|
|
setToken('')
|
|
setTokenSet(Boolean(s.token_set))
|
|
setTokenHint(s.token_hint || null)
|
|
setLastError(s.last_error || null)
|
|
setMsg({ type: 'ok', text: 'Gespeichert. Das Token bleibt nur auf dem Server.' })
|
|
} catch (e) {
|
|
setMsg({ type: 'err', text: e.message })
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
const test = async () => {
|
|
setBusy(true)
|
|
setMsg(null)
|
|
try {
|
|
const res = await api.testTandoorConnection({
|
|
base_url: baseUrl || undefined,
|
|
token: token.trim() || undefined,
|
|
})
|
|
const who = res.user ? ` als ${res.user}` : ''
|
|
setMsg({ type: 'ok', text: `Verbindung ok${who}.` })
|
|
setLastError(null)
|
|
setLastOk(new Date().toISOString())
|
|
} catch (e) {
|
|
setMsg({ type: 'err', text: e.message })
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
const clear = async () => {
|
|
if (!confirm('Tandoor-Zugang wirklich entfernen?')) return
|
|
setBusy(true)
|
|
setMsg(null)
|
|
try {
|
|
await api.putTandoorSettings({ base_url: '', clear_token: true })
|
|
setBaseUrl('')
|
|
setToken('')
|
|
setTokenSet(false)
|
|
setTokenHint(null)
|
|
setLastOk(null)
|
|
setLastError(null)
|
|
setMsg({ type: 'ok', text: 'Zugang entfernt.' })
|
|
} catch (e) {
|
|
setMsg({ type: 'err', text: e.message })
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="card section-gap">
|
|
<div className="card-title">Tandoor (Familienrezepte)</div>
|
|
<p style={{ fontSize: 13, color: 'var(--text2)', marginBottom: 14, lineHeight: 1.6 }}>
|
|
Persönlicher Zugang zu eurem Tandoor — nicht systemweit. Gerichte bleiben dort; Mitai rechnet später Nährwerte.
|
|
Token unter Tandoor → Einstellungen → API-Tokens anlegen.
|
|
</p>
|
|
<div className="settings-page__field">
|
|
<label className="settings-page__field-label" htmlFor="tandoor-url">Adresse</label>
|
|
<input
|
|
id="tandoor-url"
|
|
className="form-input"
|
|
type="url"
|
|
placeholder="https://tandoor.stommer.de"
|
|
value={baseUrl}
|
|
onChange={(e) => setBaseUrl(e.target.value)}
|
|
autoComplete="off"
|
|
/>
|
|
</div>
|
|
<div className="settings-page__field">
|
|
<label className="settings-page__field-label" htmlFor="tandoor-token">API-Token</label>
|
|
<input
|
|
id="tandoor-token"
|
|
className="form-input"
|
|
type="password"
|
|
placeholder={tokenSet ? `Gespeichert (${tokenHint || '…'}) — leer lassen zum Behalten` : 'Token einfügen'}
|
|
value={token}
|
|
onChange={(e) => setToken(e.target.value)}
|
|
autoComplete="new-password"
|
|
/>
|
|
</div>
|
|
{lastOk && (
|
|
<p style={{ fontSize: 12, color: 'var(--accent-dark)', margin: '0 0 8px' }}>
|
|
Zuletzt erfolgreich: {String(lastOk).slice(0, 16).replace('T', ' ')}
|
|
</p>
|
|
)}
|
|
{lastError && !msg && (
|
|
<p style={{ fontSize: 12, color: 'var(--danger)', margin: '0 0 8px' }}>Letzter Fehler: {lastError}</p>
|
|
)}
|
|
{msg && (
|
|
<div style={{ fontSize: 13, color: msg.type === 'err' ? 'var(--danger)' : 'var(--accent-dark)', marginBottom: 10 }}>
|
|
{msg.text}
|
|
</div>
|
|
)}
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
<button type="button" className="btn btn-primary" disabled={busy} onClick={save}>Speichern</button>
|
|
<button type="button" className="btn btn-secondary" disabled={busy} onClick={test}>Verbindung testen</button>
|
|
{tokenSet && (
|
|
<button type="button" className="btn btn-secondary" disabled={busy} onClick={clear}>Zugang entfernen</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|