diff --git a/backend/build_deb.sh b/backend/build_deb.sh index 210fe27..0d06758 100644 --- a/backend/build_deb.sh +++ b/backend/build_deb.sh @@ -127,9 +127,11 @@ configure_ml_python_setup() { NSFWAPP_ML_SETUP=1 NSFWAPP_ML_REQUIREMENTS="$requirements_file" - NSFWAPP_ML_VENV="${NSFWAPP_ML_VENV:-$app_dir/ml-venv}" - NSFWAPP_ML_SETUP_LOG="${NSFWAPP_ML_SETUP_LOG:-$app_dir/ml-setup.log}" - export NSFWAPP_ML_SETUP NSFWAPP_ML_REQUIREMENTS NSFWAPP_ML_VENV NSFWAPP_ML_SETUP_LOG + ml_tmp_root="${TMPDIR:-/tmp}" + ml_tmp_user="${USER:-$(id -un 2>/dev/null || echo user)}" + ml_tmp_user="$(printf '%s' "$ml_tmp_user" | tr -c 'A-Za-z0-9_.-' '_')" + NSFWAPP_ML_VENV="${NSFWAPP_ML_VENV:-$ml_tmp_root/nsfwapp-ml-venv-$ml_tmp_user}" + export NSFWAPP_ML_SETUP NSFWAPP_ML_REQUIREMENTS NSFWAPP_ML_VENV return 0 } @@ -288,8 +290,8 @@ User-Daten: Wichtige Laufzeitprogramme: ffmpeg/ffprobe muessen fuer Recording, Teaser, Analyse und Videofunktionen verfuegbar sein. PostgreSQL ist die Datenbank. Fuer lokale Datenordner werden initdb/pg_ctl und fuer Migration/Backup pg_dump/pg_restore benoetigt. - Der Launcher erstellt beim ersten Start automatisch ${NSFWAPP_HOME:-$HOME/nsfwapp}/ml-venv - und installiert die mitgelieferte requirements-ml.txt. Details stehen in ml-setup.log. + Der Launcher erstellt beim ersten Start automatisch eine ML-Python-Umgebung unter ${TMPDIR:-/tmp}/nsfwapp-ml-venv- + und installiert die mitgelieferte requirements-ml.txt. Details stehen in recorder.log. Setze AI_SERVER_PYTHON, wenn du eine eigene Python-Umgebung verwenden willst. EOF diff --git a/backend/dist/nsfwapp-linux-amd64 b/backend/dist/nsfwapp-linux-amd64 index 0ffc542..68a2f06 100644 Binary files a/backend/dist/nsfwapp-linux-amd64 and b/backend/dist/nsfwapp-linux-amd64 differ diff --git a/backend/dist/nsfwapp.exe b/backend/dist/nsfwapp.exe index e304c1b..72f7573 100644 Binary files a/backend/dist/nsfwapp.exe and b/backend/dist/nsfwapp.exe differ diff --git a/backend/dist/nsfwapp_amd64.deb b/backend/dist/nsfwapp_amd64.deb index 7173f0b..00c0631 100644 Binary files a/backend/dist/nsfwapp_amd64.deb and b/backend/dist/nsfwapp_amd64.deb differ diff --git a/backend/ml_setup.go b/backend/ml_setup.go index 068e978..93cd651 100644 --- a/backend/ml_setup.go +++ b/backend/ml_setup.go @@ -23,7 +23,6 @@ type mlPythonSetupStatus struct { Phase string `json:"phase"` Message string `json:"message"` Error string `json:"error,omitempty"` - LogFile string `json:"logFile,omitempty"` StartedAt string `json:"startedAt,omitempty"` UpdatedAt string `json:"updatedAt,omitempty"` FinishedAt string `json:"finishedAt,omitempty"` @@ -31,11 +30,11 @@ type mlPythonSetupStatus struct { type mlPythonSetupConfig struct { enabled bool + appDir string requirementsPath string venvDir string venvPython string requirementsMark string - logPath string } var ( @@ -60,7 +59,6 @@ func initMLPythonSetupStatus() { Progress: 0, Phase: "pending", Message: "ML-Python-Umgebung wartet auf Start.", - LogFile: filepath.Base(cfg.logPath), UpdatedAt: now, } return @@ -106,24 +104,58 @@ func mlPythonSetupConfigFromEnv() mlPythonSetupConfig { venvDir := strings.TrimSpace(os.Getenv("NSFWAPP_ML_VENV")) if venvDir == "" { - venvDir = filepath.Join(appDir, "ml-venv") - } - - logPath := strings.TrimSpace(os.Getenv("NSFWAPP_ML_SETUP_LOG")) - if logPath == "" { - logPath = filepath.Join(appDir, "ml-setup.log") + venvDir = defaultMLPythonVenvDir() } return mlPythonSetupConfig{ enabled: true, + appDir: filepath.Clean(appDir), requirementsPath: filepath.Clean(requirementsPath), venvDir: filepath.Clean(venvDir), venvPython: filepath.Join(filepath.Clean(venvDir), "bin", "python"), requirementsMark: filepath.Join(filepath.Clean(venvDir), ".requirements-ml.txt"), - logPath: filepath.Clean(logPath), } } +func defaultMLPythonVenvDir() string { + base := strings.TrimSpace(os.TempDir()) + if base == "" { + base = "." + } + + user := strings.TrimSpace(os.Getenv("USER")) + if user == "" { + user = strings.TrimSpace(os.Getenv("USERNAME")) + } + suffix := "nsfwapp-ml-venv" + if user != "" { + suffix += "-" + sanitizeMLPythonTempName(user) + } + + return filepath.Join(base, suffix) +} + +func sanitizeMLPythonTempName(raw string) string { + var b strings.Builder + for _, r := range strings.TrimSpace(raw) { + if (r >= 'a' && r <= 'z') || + (r >= 'A' && r <= 'Z') || + (r >= '0' && r <= '9') || + r == '_' || + r == '-' || + r == '.' { + b.WriteRune(r) + } else if b.Len() > 0 { + b.WriteByte('_') + } + } + out := strings.Trim(b.String(), "_-.") + if out == "" { + return "user" + } + return out +} + func mlPythonSetupStatusSnapshot() mlPythonSetupStatus { mlSetupMu.RLock() defer mlSetupMu.RUnlock() @@ -171,7 +203,6 @@ func ensureMLPythonSetup(ctx context.Context) error { s.Phase = "prepare" s.Message = "ML-Python-Umgebung wird vorbereitet." s.Error = "" - s.LogFile = filepath.Base(cfg.logPath) s.StartedAt = startedAt s.FinishedAt = "" }) @@ -184,9 +215,6 @@ func ensureMLPythonSetup(ctx context.Context) error { if err := os.MkdirAll(cfg.venvDir, 0755); err != nil { return finishMLPythonSetupError("ML-Venv-Ordner konnte nicht erstellt werden.", err) } - if err := os.MkdirAll(filepath.Dir(cfg.logPath), 0755); err != nil { - return finishMLPythonSetupError("ML-Setup-Logordner konnte nicht erstellt werden.", err) - } if current, err := os.ReadFile(cfg.requirementsMark); err == nil && string(current) == string(reqBytes) { if fi, statErr := os.Stat(cfg.venvPython); statErr == nil && fi != nil && !fi.IsDir() { @@ -231,16 +259,10 @@ func runMLPythonSetupCommand(ctx context.Context, cfg mlPythonSetupConfig, phase s.Message = message }) - logFile, err := os.OpenFile(cfg.logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return err - } - defer logFile.Close() - - _, _ = logFile.WriteString("\n[" + time.Now().Format(time.RFC3339) + "] " + name + " " + strings.Join(args, " ") + "\n") + appLogln("[ml-setup] start:", name, strings.Join(args, " ")) cmd := exec.CommandContext(ctx, name, args...) - cmd.Dir = filepath.Dir(cfg.logPath) + cmd.Dir = cfg.appDir stdout, err := cmd.StdoutPipe() if err != nil { @@ -263,7 +285,7 @@ func runMLPythonSetupCommand(ctx context.Context, cfg mlPythonSetupConfig, phase scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) for scanner.Scan() { line := scanner.Text() - _, _ = logFile.WriteString(line + "\n") + appLogln("[ml-setup]", line) select { case lines <- line: case <-ctx.Done(): @@ -297,10 +319,14 @@ func runMLPythonSetupCommand(ctx context.Context, cfg mlPythonSetupConfig, phase } lineCount++ progress := startProgress + minInt(progressRange-1, lineCount) + visibleMessage := line + if phase == "requirements" { + visibleMessage = message + } updateMLPythonSetupStatus(func(s *mlPythonSetupStatus) { s.Progress = progress s.Phase = phase - s.Message = line + s.Message = visibleMessage }) case err := <-done: diff --git a/backend/postgres_local.go b/backend/postgres_local.go index cfe0f8c..4373381 100644 --- a/backend/postgres_local.go +++ b/backend/postgres_local.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "net/url" "os" "os/exec" @@ -17,7 +18,9 @@ const ( postgresModeURL = "url" postgresModeLocal = "local" - defaultPostgresDataDir = "postgres-data" + postgresDataDirName = "nsfwapp-postgres-data" + legacyPostgresDataDir = "postgres-data" + defaultPostgresDataDir = postgresDataDirName defaultPostgresPort = 55432 defaultPostgresDatabase = "nsfwapp" defaultPostgresUser = "nsfwapp" @@ -39,7 +42,20 @@ func normalizePostgresDataDir(raw string) string { if value == "" { value = defaultPostgresDataDir } - return filepath.Clean(filepath.FromSlash(value)) + value = filepath.Clean(filepath.FromSlash(value)) + + base := filepath.Base(value) + if strings.EqualFold(base, postgresDataDirName) { + return value + } + if strings.EqualFold(base, legacyPostgresDataDir) { + parent := filepath.Dir(value) + if parent == "." || parent == string(filepath.Separator) { + return postgresDataDirName + } + return filepath.Join(parent, postgresDataDirName) + } + return filepath.Join(value, postgresDataDirName) } func normalizePostgresPort(port int) int { @@ -106,7 +122,7 @@ func postgresSettingsKey(s RecorderSettings) string { func buildLocalPostgresDSN(s RecorderSettings) (string, error) { normalizePostgresSettings(&s) - dataDir, err := resolvePathRelativeToApp(s.PostgresDataDir) + dataDir, err := localPostgresDataDirAbs(s) if err != nil { return "", err } @@ -139,7 +155,7 @@ func ensureLocalPostgresDataDir(s RecorderSettings) (string, error) { return "", nil } - dataDir, err := resolvePathRelativeToApp(s.PostgresDataDir) + dataDir, err := localPostgresDataDirAbs(s) if err != nil { return "", err } @@ -149,6 +165,11 @@ func ensureLocalPostgresDataDir(s RecorderSettings) (string, error) { return dataDir, nil } +func localPostgresDataDirAbs(s RecorderSettings) (string, error) { + normalizePostgresSettings(&s) + return resolvePathRelativeToApp(s.PostgresDataDir) +} + func ensureLocalPostgresDataDirPath(dataDir string) error { dataDir = filepath.Clean(strings.TrimSpace(dataDir)) if dataDir == "" { @@ -167,6 +188,209 @@ func ensureLocalPostgresDataDirPath(dataDir string) error { return nil } +func moveLocalPostgresDataDirIfNeeded(current, next RecorderSettings) (bool, error) { + normalizePostgresSettings(¤t) + normalizePostgresSettings(&next) + if current.PostgresMode != postgresModeLocal || next.PostgresMode != postgresModeLocal { + return false, nil + } + + oldDir, err := localPostgresDataDirAbs(current) + if err != nil { + return false, err + } + newDir, err := localPostgresDataDirAbs(next) + if err != nil { + return false, err + } + oldDir = filepath.Clean(oldDir) + newDir = filepath.Clean(newDir) + if samePath(oldDir, newDir) { + return false, nil + } + + oldHasCluster, err := localPostgresDataDirHasCluster(oldDir) + if err != nil { + return false, err + } + if !oldHasCluster { + return false, nil + } + + newHasCluster, err := localPostgresDataDirHasCluster(newDir) + if err != nil { + return false, err + } + if newHasCluster { + return false, appErrorf("Ziel enthält bereits einen PostgreSQL-Datenordner: %s", newDir) + } + if exists, empty, err := dirExistsEmpty(newDir); err != nil { + return false, err + } else if exists && !empty { + return false, appErrorf("Zielordner ist nicht leer: %s", newDir) + } else if exists && empty { + if err := os.Remove(newDir); err != nil { + return false, err + } + } + + appLogln("PostgreSQL-Datenordner wird verschoben:", oldDir, "->", newDir) + if store := getModelStore(); store != nil { + _ = store.Close() + } + + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + if err := stopLocalPostgresCluster(ctx, oldDir); err != nil { + return false, err + } + + if err := moveDir(oldDir, newDir); err != nil { + return false, err + } + appLogln("PostgreSQL-Datenordner verschoben:", newDir) + return true, nil +} + +func localPostgresDataDirHasCluster(dataDir string) (bool, error) { + dataDir = filepath.Clean(strings.TrimSpace(dataDir)) + if dataDir == "" { + return false, nil + } + versionFile := filepath.Join(dataDir, "PG_VERSION") + info, err := os.Stat(versionFile) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return info != nil && !info.IsDir(), nil +} + +func dirExistsEmpty(path string) (bool, bool, error) { + info, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return false, false, nil + } + return false, false, err + } + if !info.IsDir() { + return true, false, appErrorf("Ziel ist kein Ordner: %s", path) + } + entries, err := os.ReadDir(path) + if err != nil { + return true, false, err + } + return true, len(entries) == 0, nil +} + +func samePath(a, b string) bool { + aa, errA := filepath.Abs(filepath.Clean(a)) + bb, errB := filepath.Abs(filepath.Clean(b)) + if errA == nil { + a = aa + } + if errB == nil { + b = bb + } + if strings.EqualFold(a, b) { + return true + } + return a == b +} + +func stopLocalPostgresCluster(ctx context.Context, dataDir string) error { + pgCtl, err := postgresToolPath("pg_ctl") + if err != nil { + return appErrorf("pg_ctl wurde nicht gefunden. Installiere PostgreSQL-Server-Tools oder nimm pg_ctl in PATH auf.") + } + out, err := runLocalPostgresTool(ctx, pgCtl, "-D", dataDir, "-w", "stop", "-m", "fast") + if err == nil { + return nil + } + lower := strings.ToLower(out) + if strings.Contains(lower, "no server running") || strings.Contains(lower, "pid file") && strings.Contains(lower, "does not exist") { + return nil + } + return appErrorf("lokaler PostgreSQL-Server konnte nicht gestoppt werden: %s", out) +} + +func moveDir(src, dst string) error { + src = filepath.Clean(src) + dst = filepath.Clean(dst) + if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { + return err + } + if err := os.Rename(src, dst); err == nil { + return nil + } + + tmp := dst + ".moving-" + strconv.FormatInt(time.Now().UnixNano(), 10) + if err := copyDir(src, tmp); err != nil { + _ = os.RemoveAll(tmp) + return err + } + if err := os.Rename(tmp, dst); err != nil { + _ = os.RemoveAll(tmp) + return err + } + return os.RemoveAll(src) +} + +func copyDir(src, dst string) error { + src = filepath.Clean(src) + dst = filepath.Clean(dst) + return filepath.WalkDir(src, func(path string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + rel, err := filepath.Rel(src, path) + if err != nil { + return err + } + target := filepath.Join(dst, rel) + info, err := entry.Info() + if err != nil { + return err + } + mode := info.Mode() + if entry.IsDir() { + return os.MkdirAll(target, mode.Perm()) + } + if mode&os.ModeSymlink != 0 { + linkTarget, err := os.Readlink(path) + if err != nil { + return err + } + return os.Symlink(linkTarget, target) + } + return copyRegularFile(path, target, mode.Perm()) + }) +} + +func copyRegularFile(src, dst string, perm os.FileMode) error { + if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { + return err + } + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm) + if err != nil { + return err + } + if _, err := io.Copy(out, in); err != nil { + _ = out.Close() + return err + } + return out.Close() +} + func ensureLocalPostgresCluster(ctx context.Context, dataDir string, port int, user, database string) error { dataDir = filepath.Clean(strings.TrimSpace(dataDir)) if err := ensureLocalPostgresDataDirPath(dataDir); err != nil { diff --git a/backend/settings.go b/backend/settings.go index 3694ef2..4a98bb9 100644 --- a/backend/settings.go +++ b/backend/settings.go @@ -495,12 +495,6 @@ func recordSettingsHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Database URL fehlt", http.StatusBadRequest) return } - if in.PostgresMode == postgresModeLocal { - if _, err := ensureLocalPostgresDataDir(in.RecorderSettings); err != nil { - http.Error(w, "konnte PostgreSQL-Datenordner nicht erstellen: "+err.Error(), http.StatusBadRequest) - return - } - } // 2) Migration: wenn in.DatabaseURL ein Passwort enthält, extrahieren // und URL ohne Passwort zurückschreiben. @@ -583,11 +577,24 @@ func recordSettingsHandler(w http.ResponseWriter, r *http.Request) { next.TrainingRecognitionEnabled = in.TrainingRecognitionEnabled next.TrainingDetectorEpochs = in.TrainingDetectorEpochs + postgresDataMoved, err := moveLocalPostgresDataDirIfNeeded(current, next) + if err != nil { + http.Error(w, "PostgreSQL-Datenordner konnte nicht verschoben werden: "+err.Error(), http.StatusBadRequest) + return + } + if next.PostgresMode == postgresModeLocal && !postgresDataMoved { + if _, err := ensureLocalPostgresDataDir(next); err != nil { + http.Error(w, "konnte PostgreSQL-Datenordner nicht erstellen: "+err.Error(), http.StatusBadRequest) + return + } + } + dbChanged := normalizeDatabaseType(next.DatabaseType) != normalizeDatabaseType(current.DatabaseType) || postgresSettingsKey(next) != postgresSettingsKey(current) || strings.TrimSpace(next.DatabaseURL) != strings.TrimSpace(current.DatabaseURL) || - strings.TrimSpace(next.EncryptedDBPassword) != strings.TrimSpace(current.EncryptedDBPassword) + strings.TrimSpace(next.EncryptedDBPassword) != strings.TrimSpace(current.EncryptedDBPassword) || + postgresDataMoved var newStore *ModelStore diff --git a/backend/settings_database_migrate.go b/backend/settings_database_migrate.go index 67b9768..52889f3 100644 --- a/backend/settings_database_migrate.go +++ b/backend/settings_database_migrate.go @@ -25,6 +25,7 @@ func settingsDatabaseMigrateHandler(w http.ResponseWriter, r *http.Request) { } current := getSettings() + normalizePostgresSettings(¤t) target := current target.DatabaseType = databaseTypePostgres target.PostgresMode = in.PostgresMode @@ -40,6 +41,50 @@ func settingsDatabaseMigrateHandler(w http.ResponseWriter, r *http.Request) { return } + if current.PostgresMode == postgresModeLocal && target.PostgresMode == postgresModeLocal { + moved, err := moveLocalPostgresDataDirIfNeeded(current, target) + if err != nil { + http.Error(w, "PostgreSQL-Datenordner konnte nicht verschoben werden: "+err.Error(), http.StatusInternalServerError) + return + } + if !moved { + if _, err := ensureLocalPostgresDataDir(target); err != nil { + http.Error(w, "konnte PostgreSQL-Datenordner nicht erstellen: "+err.Error(), http.StatusInternalServerError) + return + } + } + + targetDSN, err := buildPostgresDSNFromRecorderSettings(target) + if err != nil { + http.Error(w, "Ziel-Datenbank ungueltig: "+err.Error(), http.StatusBadRequest) + return + } + + newStore := NewModelStore(ModelStoreConfig{Type: databaseTypePostgres, DSN: targetDSN}) + if err := newStore.Load(); err != nil { + _ = newStore.Close() + http.Error(w, "Ziel-Datenbank konnte nach Verschieben nicht geladen werden: "+err.Error(), http.StatusInternalServerError) + return + } + + settingsMu.Lock() + settings = target + settingsMu.Unlock() + saveSettingsToDisk() + + setModelStore(newStore) + setCoverModelStore(newStore) + setChaturbateOnlineModelStore(newStore) + + writeJSON(w, http.StatusOK, map[string]any{ + "ok": true, + "bytes": 0, + "mode": target.PostgresMode, + "moved": moved, + }) + return + } + sanitizedURL, pwFromURL := stripPasswordFromPostgresURL(target.DatabaseURL) pwFromURL = strings.TrimSpace(pwFromURL) if pwFromURL == "****" { diff --git a/frontend/src/components/ui/LoginPage.tsx b/frontend/src/components/ui/LoginPage.tsx index 0e02788..50c32df 100644 --- a/frontend/src/components/ui/LoginPage.tsx +++ b/frontend/src/components/ui/LoginPage.tsx @@ -50,7 +50,6 @@ type MLSetupStatus = { phase?: string message?: string error?: string - logFile?: string } type SetupResp = { @@ -79,12 +78,14 @@ function MLSetupModal({ status }: { status: MLSetupStatus | null }) { const title = failed ? 'ML-Python-Setup fehlgeschlagen' : 'ML-Python-Pakete werden installiert' - const message = String(status.message || '').trim() || 'Die ML-Python-Umgebung wird vorbereitet.' + const rawMessage = String(status.message || '').trim() + const message = + !failed && status.phase === 'requirements' + ? 'ML-Python-Pakete werden installiert. Details stehen in recorder.log.' + : rawMessage || 'Die ML-Python-Umgebung wird vorbereitet.' const detail = failed ? String(status.error || '').trim() - : status.logFile - ? `Log: ${status.logFile}` - : '' + : '' return (
{title}
-
{message}
+
+ {message} +
{Math.round(progress)}% diff --git a/frontend/src/components/ui/RecorderSettings.tsx b/frontend/src/components/ui/RecorderSettings.tsx index 66e96d4..1c98228 100644 --- a/frontend/src/components/ui/RecorderSettings.tsx +++ b/frontend/src/components/ui/RecorderSettings.tsx @@ -284,10 +284,13 @@ function SettingsSection(props: { ) } +const POSTGRES_DATA_DIR_NAME = 'nsfwapp-postgres-data' +const LEGACY_POSTGRES_DATA_DIR_NAME = 'postgres-data' + const DEFAULTS: RecorderSettings = { databaseType: 'postgres', postgresMode: 'local', - postgresDataDir: 'postgres-data', + postgresDataDir: POSTGRES_DATA_DIR_NAME, postgresPort: 55432, postgresDatabase: 'nsfwapp', postgresUser: 'nsfwapp', @@ -558,7 +561,21 @@ function normalizePostgresMode(value: unknown, databaseUrl?: unknown): 'url' | ' } function normalizePostgresDataDir(value: unknown) { - return String(value ?? '').trim() || 'postgres-data' + let s = String(value ?? '').trim() + if (!s) return POSTGRES_DATA_DIR_NAME + + s = s.replace(/[\\/]+$/g, '') + if (!s) return POSTGRES_DATA_DIR_NAME + + const sep = s.includes('\\') && !s.includes('/') ? '\\' : '/' + const parts = s.split(/[\\/]+/) + const base = parts[parts.length - 1] || '' + if (base.toLowerCase() === POSTGRES_DATA_DIR_NAME.toLowerCase()) return s + if (base.toLowerCase() === LEGACY_POSTGRES_DATA_DIR_NAME.toLowerCase()) { + const parent = parts.slice(0, -1).join(sep) + return parent ? `${parent}${sep}${POSTGRES_DATA_DIR_NAME}` : POSTGRES_DATA_DIR_NAME + } + return `${s}${sep}${POSTGRES_DATA_DIR_NAME}` } function normalizePostgresPort(value: unknown) { @@ -737,7 +754,7 @@ export default function RecorderSettings({ onAssetsGenerated }: Props) { const [dbModalOpen, setDbModalOpen] = useState(false) const [loadedDatabaseType, setLoadedDatabaseType] = useState<'postgres'>('postgres') const [loadedPostgresMode, setLoadedPostgresMode] = useState<'url' | 'local'>('local') - const [loadedPostgresDataDir, setLoadedPostgresDataDir] = useState('postgres-data') + const [loadedPostgresDataDir, setLoadedPostgresDataDir] = useState(POSTGRES_DATA_DIR_NAME) const [loadedPostgresPort, setLoadedPostgresPort] = useState(55432) const [loadedPostgresDatabase, setLoadedPostgresDatabase] = useState('nsfwapp') const [loadedPostgresUser, setLoadedPostgresUser] = useState('nsfwapp') @@ -1318,7 +1335,7 @@ export default function RecorderSettings({ onAssetsGenerated }: Props) { setValue((v) => { if (target === 'record') return { ...v, recordDir: p } if (target === 'done') return { ...v, doneDir: p } - if (target === 'postgresData') return { ...v, postgresMode: 'local', postgresDataDir: p } + if (target === 'postgresData') return { ...v, postgresMode: 'local', postgresDataDir: normalizePostgresDataDir(p) } return { ...v, ffmpegPath: p } }) } catch (e: any) { @@ -1504,6 +1521,16 @@ export default function RecorderSettings({ onAssetsGenerated }: Props) { setLoadedPostgresUser(postgresUser) setLoadedDatabaseUrl(databaseUrl) setPendingDbPassword('') + setValue((v) => ({ + ...v, + databaseType: 'postgres', + postgresMode, + postgresDataDir, + postgresPort, + postgresDatabase, + postgresUser, + databaseUrl, + })) window.dispatchEvent(new CustomEvent('recorder-settings-updated')) window.dispatchEvent(new Event('models-changed')) @@ -1661,6 +1688,16 @@ export default function RecorderSettings({ onAssetsGenerated }: Props) { saveSuccessTimerRef.current = null }, 2500) window.dispatchEvent(new CustomEvent('recorder-settings-updated')) + setValue((v) => ({ + ...v, + databaseType, + postgresMode, + postgresDataDir, + postgresPort, + postgresDatabase, + postgresUser, + databaseUrl, + })) const databaseChanged = postgresSourceKey({ @@ -2433,9 +2470,9 @@ export default function RecorderSettings({ onAssetsGenerated }: Props) {
setValue((v) => ({ ...v, postgresMode: 'local', postgresDataDir: e.target.value }))} - placeholder="postgres-data" + placeholder={POSTGRES_DATA_DIR_NAME} className="min-w-0 flex-1 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"