182 lines
6.8 KiB
TypeScript
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>
|
|
)
|
|
} |