nsfwapp/frontend/src/components/ui/PostgresUrlModal.tsx
2026-03-03 10:11:30 +01:00

182 lines
6.8 KiB
TypeScript

// frontend\src\components\ui\PostgresUrlModal.tsx
'use client'
import { useMemo, useState } from 'react'
import Modal from './Modal'
import Button from './Button'
type Props = {
open: boolean
onClose: () => void
initialUrl?: string
initialHasPassword?: boolean
onApply: (v: { databaseUrl: string; dbPassword: string }) => void
}
function safeInt(v: any, fallback: number) {
const n = Number(v)
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback
}
function buildPostgresUrl(opts: {
user: string
host: string
port: number
db: string
sslmode: string
}) {
const user = (opts.user || '').trim() || 'postgres'
const host = (opts.host || '').trim() || '127.0.0.1'
const port = safeInt(opts.port, 5432)
const db = (opts.db || '').trim() || 'postgres'
const sslmode = (opts.sslmode || '').trim() || 'disable'
// Passwort absichtlich NICHT in URL
return `postgres://${encodeURIComponent(user)}@${host}:${port}/${encodeURIComponent(db)}?sslmode=${encodeURIComponent(
sslmode
)}`
}
export default function PostgresUrlModal({
open,
onClose,
initialUrl,
initialHasPassword,
onApply,
}: Props) {
// Defaults (du kannst später optional initialUrl parsen, ist aber nicht nötig)
const [host, setHost] = useState('127.0.0.1')
const [port, setPort] = useState(5432)
const [db, setDb] = useState('nsfwapp')
const [user, setUser] = useState('postgres')
const [password, setPassword] = useState('')
const [sslmode, setSslmode] = useState<'disable' | 'require' | 'verify-full'>('disable')
const preview = useMemo(() => buildPostgresUrl({ user, host, port, db, sslmode }), [user, host, port, db, sslmode])
const footer = (
<>
<Button variant="secondary" onClick={onClose}>
Abbrechen
</Button>
<Button
variant="primary"
onClick={() => {
onApply({ databaseUrl: preview, dbPassword: password })
onClose()
}}
>
Übernehmen
</Button>
</>
)
return (
<Modal
open={open}
onClose={onClose}
title="Postgres URL erstellen"
width="max-w-2xl"
footer={footer}
>
<div className="px-4 pb-4 sm:px-6 sm:pb-6 space-y-4">
<div className="text-xs text-gray-600 dark:text-gray-300">
Passwort wird <span className="font-semibold">verschlüsselt</span> gespeichert (nicht in der URL).
{initialUrl ? (
<>
<div className="mt-1">
Aktuelle URL: <code className="break-all">{initialUrl}</code>
</div>
</>
) : null}
{initialHasPassword ? <div className="mt-1">Passwort: gespeichert</div> : null}
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12 sm:items-center">
<label className="text-sm font-medium text-gray-900 dark:text-gray-200 sm:col-span-3">Host</label>
<input
value={host}
onChange={(e) => setHost(e.target.value)}
className="sm:col-span-9 rounded-lg px-3 py-2 text-sm bg-white text-gray-900 ring-1 ring-gray-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
dark:bg-white/10 dark:text-white dark:ring-white/10"
placeholder="127.0.0.1"
/>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12 sm:items-center">
<label className="text-sm font-medium text-gray-900 dark:text-gray-200 sm:col-span-3">Port</label>
<input
type="number"
min={1}
max={65535}
value={port}
onChange={(e) => setPort(safeInt(e.target.value, 5432))}
className="sm:col-span-9 rounded-lg px-3 py-2 text-sm bg-white text-gray-900 ring-1 ring-gray-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
dark:bg-white/10 dark:text-white dark:ring-white/10"
/>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12 sm:items-center">
<label className="text-sm font-medium text-gray-900 dark:text-gray-200 sm:col-span-3">Datenbank</label>
<input
value={db}
onChange={(e) => setDb(e.target.value)}
className="sm:col-span-9 rounded-lg px-3 py-2 text-sm bg-white text-gray-900 ring-1 ring-gray-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
dark:bg-white/10 dark:text-white dark:ring-white/10"
placeholder="nsfwapp"
/>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12 sm:items-center">
<label className="text-sm font-medium text-gray-900 dark:text-gray-200 sm:col-span-3">User</label>
<input
value={user}
onChange={(e) => setUser(e.target.value)}
className="sm:col-span-9 rounded-lg px-3 py-2 text-sm bg-white text-gray-900 ring-1 ring-gray-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
dark:bg-white/10 dark:text-white dark:ring-white/10"
placeholder="postgres"
/>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12 sm:items-center">
<label className="text-sm font-medium text-gray-900 dark:text-gray-200 sm:col-span-3">Passwort</label>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password"
className="sm:col-span-9 rounded-lg px-3 py-2 text-sm bg-white text-gray-900 ring-1 ring-gray-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
dark:bg-white/10 dark:text-white dark:ring-white/10"
placeholder="••••••••"
/>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12 sm:items-center">
<label className="text-sm font-medium text-gray-900 dark:text-gray-200 sm:col-span-3">SSL</label>
<select
value={sslmode}
onChange={(e) => setSslmode(e.target.value as any)}
className="sm:col-span-9 h-10 rounded-lg px-3 text-sm bg-white text-gray-900 ring-1 ring-gray-200 shadow-sm
dark:border-white/10 dark:bg-gray-900 dark:text-gray-100 dark:[color-scheme:dark]"
>
<option value="disable">disable</option>
<option value="require">require</option>
<option value="verify-full">verify-full</option>
</select>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-200">
<div className="font-semibold mb-1">Vorschau</div>
<code className="break-all">{preview}</code>
</div>
</div>
</Modal>
)
}