Konektory do Postgresu, pristupove udaje sifrovane
Pristupove udaje konektoru se ukladaji do databaze a prezijou restart. Popis v documentation/14-databaze.md. Databaze je volitelna a rezimy jsou oddelene: - postgres kdyz je DATABASE_URL i SECRETS_KEY - memory jinak, tedy pri nasazenem mockupu a lokalnim vyvoji bez DB Rozhodnuti je jen na jednom miste (src/data/connectorStore.ts). Nikde jinde se nezjistuje, jestli databaze je - kdyby se to rozlezlo po kodu, jedno misto by se zapomnelo a chovalo by se pak jinak nez zbytek. Chybejici databaze nesmi shodit start: container, ktery nenastartuje, je pro AppFactory nefunkcni sluzba. Misto toho se do logu napise proc a portal to ukaze na strance Konektory. Stejne tak kdyz migrace selzou - psat do rozbiteho schematu je horsi nez neukladat. Databaze potrebuje oboji. Bez SECRETS_KEY by se udaje ukladaly v plaintextu a to je horsi nez ztratit je pri restartu: tabulku vidi kazda zaloha a kazdy dump pri ladeni. Pridano: - pool v src/db/pool.ts vcetne transakci a dbFor(tenantId) jako sev pro budouci oddelenou databazi jednoho klienta - migrace ze src/db/migrations/*.sql pod pg_advisory_lock, jinak je pri rolling deployi pusti vsechny instance naraz. Jeden soubor je jedna transakce - sifrovani AES-256-GCM s nahodnym IV a verzi klice. Nerozsifrovatelna hodnota nepada, chova se jako nevyplnena a zaloguje se - jeden rozbity konektor nesmi shodit seznam ostatnich - /health/ready s pingem do DB. /health na databazi zamerne nezavisi, kratky vypadek by jinak vedl k restartovani containeru - GET /api/dashboard/storage a hlaska v portalu o tom, ze data jsou jen v pameti - jediny vychozi konektor na firmu a sluzbu hlida castecny unikatni index, ne jen kod. Dva soubezne zapisy by jinak udelaly dva vychozi Zmeneno: cteni i zapis konektoru je asynchronni, vcetne validace stromu. Overeno proti Postgresu 16 v kontejneru: migrace, sifrovani v tabulce, preziti restartu, rozsifrovani spravnym klicem, degradace pri spatnem klici, PATCH bez tajneho pole, prepnuti a smazani vychoziho konektoru, pametovy rezim bez DATABASE_URL. Kontejner po overeni smazan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3279dd7dac
commit
78e7f99d60
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
Eye,
|
||||
EyeOff,
|
||||
@@ -26,6 +27,7 @@ import type {
|
||||
Service,
|
||||
ServiceCredentialField,
|
||||
ServiceOverview,
|
||||
StorageStatus,
|
||||
} from '@/types/dashboard';
|
||||
|
||||
/**
|
||||
@@ -46,6 +48,7 @@ export default function Connectors() {
|
||||
|
||||
const [services, setServices] = useState<ServiceOverview | null>(null);
|
||||
const [connectors, setConnectors] = useState<Connector[] | null>(null);
|
||||
const [storage, setStorage] = useState<StorageStatus | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -60,10 +63,12 @@ export default function Connectors() {
|
||||
Promise.all([
|
||||
apiFetch<ServiceOverview>('/api/dashboard/connectors/services'),
|
||||
apiFetch<{ items: Connector[] }>('/api/dashboard/connectors'),
|
||||
apiFetch<StorageStatus>('/api/dashboard/storage'),
|
||||
])
|
||||
.then(([overview, list]) => {
|
||||
.then(([overview, list, storageStatus]) => {
|
||||
setServices(overview);
|
||||
setConnectors(list.items);
|
||||
setStorage(storageStatus);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
setError(err instanceof Error ? err.message : 'Konektory se nepodařilo načíst.');
|
||||
@@ -111,6 +116,20 @@ export default function Connectors() {
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/*
|
||||
Kam se uklada. Bez teto hlasky se clovek divi, kam se podely jeho
|
||||
konektory po restartu, a hleda chybu v aplikaci.
|
||||
*/}
|
||||
{storage?.ephemeral && (
|
||||
<p className="flex items-start gap-2 rounded-card border border-warn-400/40 bg-warn-500/8 p-3 text-sm text-warn-400">
|
||||
<AlertTriangle className="mt-0.5 size-4 shrink-0" />
|
||||
<span>
|
||||
Přístupové údaje se ukládají jen do paměti serveru a restart je smaže.
|
||||
<span className="mt-0.5 block text-white/50">{storage.reason}</span>
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<DataState loading={loading} error={error} onRetry={load}>
|
||||
{(connectors?.length ?? 0) === 0 ? (
|
||||
<p className="py-10 text-center text-sm text-white/45">
|
||||
|
||||
@@ -292,6 +292,15 @@ export interface Connector {
|
||||
ready: boolean;
|
||||
}
|
||||
|
||||
/** Kam se uklada a jestli to prezije restart. */
|
||||
export interface StorageStatus {
|
||||
mode: 'postgres' | 'memory';
|
||||
/** Proc se jede v pameti. null u databaze. */
|
||||
reason: string | null;
|
||||
/** true = restart procesu data smaze. */
|
||||
ephemeral: boolean;
|
||||
}
|
||||
|
||||
export interface ConnectorTestResult {
|
||||
ok: boolean;
|
||||
/** Co se vlastne overilo: pristupove udaje, nebo jen dostupnost sluzby. */
|
||||
|
||||
Reference in New Issue
Block a user