feat(server-mode): gehostete Web-GUI fest an einen Server binden
VITE_SERVER_MODE=1 (vom Dockerfile.app gesetzt) → die App ist die Web-GUI GENAU DIESES Servers: keine Lokal/Server-Wahl (BackendChoice), kein Server-Adress-Wechsel, kein Verbinden auf andere Instanzen. Nur Login auf diesem Server. Tauri (lokale DMG) setzt die Flag NIE → behält volle Wahl (Lokal / beliebige Server-IP). - adapter.js: isServerMode-Export; im Server-Modus fest SupabaseAdapter mit Build-URL/Key, localStorage-Werte erzwungen (kein User-Override) - App.jsx: BackendChoice im Server-Modus überspringen - Login.jsx: Verbindungs-Switch + Server-Adressfeld im Server-Modus ausblenden Beide Builds verifiziert: Server-Build brennt URL ein, Normal-Build nicht. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+4
-2
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback, useRef, Suspense, lazy } from "react";
|
import React, { useState, useEffect, useCallback, useRef, Suspense, lazy } from "react";
|
||||||
import { NAV_ITEMS, defaultData } from "./constants.js";
|
import { NAV_ITEMS, defaultData } from "./constants.js";
|
||||||
import { verifyPassword, withHashedPassword, stripCredentials } from "./utils.js";
|
import { verifyPassword, withHashedPassword, stripCredentials } from "./utils.js";
|
||||||
import { storage, isCloudBackend } from "./storage/adapter.js";
|
import { storage, isCloudBackend, isServerMode } from "./storage/adapter.js";
|
||||||
import { applyMigrations } from "./storage/migrations.js";
|
import { applyMigrations } from "./storage/migrations.js";
|
||||||
import Login from "./views/Login.jsx";
|
import Login from "./views/Login.jsx";
|
||||||
import Setup from "./views/Setup.jsx";
|
import Setup from "./views/Setup.jsx";
|
||||||
@@ -464,8 +464,10 @@ export default function App() {
|
|||||||
// lokalen Daten gibt. Sobald er gewählt hat, übernimmt der jeweilige Wizard.
|
// lokalen Daten gibt. Sobald er gewählt hat, übernimmt der jeweilige Wizard.
|
||||||
// UpdateNotifier wird in allen Pre-Login-Screens mitgerendert, damit ein
|
// UpdateNotifier wird in allen Pre-Login-Screens mitgerendert, damit ein
|
||||||
// hängender Setup-Wizard sich via Auto-Update selbst befreien kann.
|
// hängender Setup-Wizard sich via Auto-Update selbst befreien kann.
|
||||||
|
// Server-Modus (gehostete Web-GUI): nie die Lokal/Server-Wahl zeigen — die
|
||||||
|
// App ist fest an diesen Server gebunden. Nur die lokale DMG zeigt die Wahl.
|
||||||
const hasChosenBackend = localStorage.getItem("rapport_backend_chosen") === "1";
|
const hasChosenBackend = localStorage.getItem("rapport_backend_chosen") === "1";
|
||||||
if (!hasChosenBackend && isNewInstall && !data.settings.setupCompleted && !currentUser) {
|
if (!isServerMode && !hasChosenBackend && isNewInstall && !data.settings.setupCompleted && !currentUser) {
|
||||||
return <><BackendChoice /><UpdateNotifier /></>;
|
return <><BackendChoice /><UpdateNotifier /></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-1
@@ -45,8 +45,17 @@ export class LocalStorageAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SERVER-MODUS: dieser Build ist die gehostete Web-GUI eines bestimmten
|
||||||
|
// Servers (gesetzt via VITE_SERVER_MODE=1 im Dockerfile.app). Dann ist die App
|
||||||
|
// FEST an diesen einen Server gebunden — keine Lokal/Server-Wahl, kein Wechsel
|
||||||
|
// der Server-Adresse, kein Verbinden auf andere Instanzen. Nur der Login auf
|
||||||
|
// genau diesem Server. Die lokale DMG (Tauri) setzt diese Flag NIE und behält
|
||||||
|
// die volle Wahl (Lokal / beliebige Server-IP).
|
||||||
|
const _isTauri = typeof window !== "undefined" && !!window.__TAURI_INTERNALS__;
|
||||||
|
export const isServerMode = import.meta.env.VITE_SERVER_MODE === "1" && !_isTauri;
|
||||||
|
|
||||||
function createAdapter() {
|
function createAdapter() {
|
||||||
const isTauri = typeof window !== "undefined" && !!window.__TAURI_INTERNALS__;
|
const isTauri = _isTauri;
|
||||||
// Build-time-URL nur für Web-Deploy gültig. Tauri-Builds ignorieren den
|
// Build-time-URL nur für Web-Deploy gültig. Tauri-Builds ignorieren den
|
||||||
// eingebrannten Wert — Desktop-User geben die Server-URL aktiv ein.
|
// eingebrannten Wert — Desktop-User geben die Server-URL aktiv ein.
|
||||||
// Der Anon-Key bleibt aus dem Build, weil er pro Cloud-Instanz konstant ist
|
// Der Anon-Key bleibt aus dem Build, weil er pro Cloud-Instanz konstant ist
|
||||||
@@ -54,6 +63,22 @@ function createAdapter() {
|
|||||||
const envUrl = isTauri ? null : import.meta.env.VITE_SUPABASE_URL;
|
const envUrl = isTauri ? null : import.meta.env.VITE_SUPABASE_URL;
|
||||||
const envKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
const envKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||||
|
|
||||||
|
// ── Server-Modus: fest auf diesen Server, ohne Wahlmöglichkeit ───────────
|
||||||
|
if (isServerMode) {
|
||||||
|
if (!envUrl || !envKey) {
|
||||||
|
console.error("VITE_SERVER_MODE=1, aber VITE_SUPABASE_URL/ANON_KEY fehlen — Fehlkonfiguration des Web-Builds.");
|
||||||
|
return new LocalStorageAdapter();
|
||||||
|
}
|
||||||
|
// localStorage konsistent halten (Login liest cloud_url für listStudios),
|
||||||
|
// aber der User kann nichts davon ändern — die Werte kommen aus dem Build.
|
||||||
|
if (typeof localStorage !== "undefined") {
|
||||||
|
localStorage.setItem("rapport_backend", "cloud");
|
||||||
|
localStorage.setItem("rapport_backend_chosen", "1");
|
||||||
|
localStorage.setItem("rapport_cloud_url", envUrl.replace(/\/+$/, ""));
|
||||||
|
}
|
||||||
|
return new SupabaseAdapter(envUrl, envKey);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof localStorage !== "undefined") {
|
if (typeof localStorage !== "undefined") {
|
||||||
// ── Auto-Recovery für 0.8.0-Upgrade-Bug ──────────────────────────────
|
// ── Auto-Recovery für 0.8.0-Upgrade-Bug ──────────────────────────────
|
||||||
// 0.8.0 hat Cloud-Modus ungewollt gesetzt bei Lokal-Usern (siehe 0.8.1
|
// 0.8.0 hat Cloud-Modus ungewollt gesetzt bei Lokal-Usern (siehe 0.8.1
|
||||||
|
|||||||
+10
-3
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { storage, isCloudBackend } from "../storage/adapter.js";
|
import { storage, isCloudBackend, isServerMode } from "../storage/adapter.js";
|
||||||
|
|
||||||
const isValidEmail = (s) => /.+@.+\..+/.test(s);
|
const isValidEmail = (s) => /.+@.+\..+/.test(s);
|
||||||
|
|
||||||
@@ -121,8 +121,10 @@ export default function Login({ verifyLogin, settings, version, cloudUnreachable
|
|||||||
const userPlaceholder = isCloud ? "name@studio.ch" : "admin";
|
const userPlaceholder = isCloud ? "name@studio.ch" : "admin";
|
||||||
const userInputType = isCloud ? "email" : "text";
|
const userInputType = isCloud ? "email" : "text";
|
||||||
|
|
||||||
const showUrlField = isCloud && editUrl;
|
// Im Server-Modus (gehostete Web-GUI) gibt es keine Server-Adress-Eingabe —
|
||||||
const showUrlBadge = isCloud && !editUrl && cloudUrl;
|
// die App ist fest an diesen Server gebunden.
|
||||||
|
const showUrlField = isCloud && editUrl && !isServerMode;
|
||||||
|
const showUrlBadge = isCloud && !editUrl && cloudUrl && !isServerMode;
|
||||||
|
|
||||||
// Hostname zur Anzeige (ohne Protokoll, ohne Port falls Standard)
|
// Hostname zur Anzeige (ohne Protokoll, ohne Port falls Standard)
|
||||||
let urlDisplay = cloudUrl;
|
let urlDisplay = cloudUrl;
|
||||||
@@ -381,6 +383,10 @@ export default function Login({ verifyLogin, settings, version, cloudUnreachable
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Verbindung-Switch + Server-Anzeige (dezent darunter) */}
|
{/* Verbindung-Switch + Server-Anzeige (dezent darunter) */}
|
||||||
|
{/* Verbindungs-Switch (Lokal/Server + Server-Adresse) nur in der lokalen
|
||||||
|
DMG. Die gehostete Web-GUI (Server-Modus) ist fest an diesen Server
|
||||||
|
gebunden — keine Wahl, kein Wechsel. */}
|
||||||
|
{!isServerMode && (
|
||||||
<div style={{
|
<div style={{
|
||||||
marginTop: 22, paddingTop: 16,
|
marginTop: 22, paddingTop: 16,
|
||||||
borderTop: "1px solid #ebe7e1",
|
borderTop: "1px solid #ebe7e1",
|
||||||
@@ -413,6 +419,7 @@ export default function Login({ verifyLogin, settings, version, cloudUnreachable
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div style={{ marginTop: 16, textAlign: "center", fontSize: 9, color: "#c8c4be", letterSpacing: "0.08em" }}>
|
<div style={{ marginTop: 16, textAlign: "center", fontSize: 9, color: "#c8c4be", letterSpacing: "0.08em" }}>
|
||||||
{version ? `V${version}` : ""}
|
{version ? `V${version}` : ""}
|
||||||
|
|||||||
Reference in New Issue
Block a user