Standardize English language usage and improve environment configuration
Replaced German text with English across the frontend UI for consistency and accessibility. Enhanced clarity in `.env.example` and `README.md`, adding detailed comments for environment variables and prerequisites. Improved documentation for setup, security, and troubleshooting.
This commit is contained in:
@@ -16,7 +16,7 @@ export function AdminUsersPage() {
|
||||
if (me?.role === "admin") load().catch((e) => setError(String(e.message || e)));
|
||||
}, [me]);
|
||||
|
||||
if (me?.role !== "admin") return <div className="card">Nur fuer Admin.</div>;
|
||||
if (me?.role !== "admin") return <div className="card">Admins only.</div>;
|
||||
|
||||
const create = async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -47,7 +47,7 @@ export function AdminUsersPage() {
|
||||
<input
|
||||
type="password"
|
||||
value={form.password}
|
||||
placeholder="passwort"
|
||||
placeholder="password"
|
||||
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||
/>
|
||||
<select value={form.role} onChange={(e) => setForm({ ...form, role: e.target.value })}>
|
||||
@@ -55,7 +55,7 @@ export function AdminUsersPage() {
|
||||
<option value="operator">operator</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
<button>User anlegen</button>
|
||||
<button>Create user</button>
|
||||
</form>
|
||||
<div className="card">
|
||||
<table>
|
||||
|
||||
@@ -26,7 +26,7 @@ export function DashboardPage() {
|
||||
};
|
||||
}, [tokens, refresh]);
|
||||
|
||||
if (loading) return <div className="card">Lade Dashboard...</div>;
|
||||
if (loading) return <div className="card">Loading dashboard...</div>;
|
||||
if (error) return <div className="card error">{error}</div>;
|
||||
|
||||
return (
|
||||
@@ -54,7 +54,7 @@ export function DashboardPage() {
|
||||
<th>Name</th>
|
||||
<th>Host</th>
|
||||
<th>DB</th>
|
||||
<th>Aktion</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -18,7 +18,7 @@ export function LoginPage() {
|
||||
await login(email, password);
|
||||
navigate("/");
|
||||
} catch {
|
||||
setError("Login fehlgeschlagen");
|
||||
setError("Login failed");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -28,12 +28,12 @@ export function LoginPage() {
|
||||
<div className="login-wrap">
|
||||
<form className="card login-card" onSubmit={submit}>
|
||||
<div className="login-eyebrow">NexaPG Monitor</div>
|
||||
<h2>Willkommen zurück</h2>
|
||||
<p className="login-subtitle">Melde dich an, um Monitoring und Query Insights zu öffnen.</p>
|
||||
<h2>Welcome back</h2>
|
||||
<p className="login-subtitle">Sign in to access monitoring and query insights.</p>
|
||||
<div className="input-shell">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="E-Mail"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autoComplete="username"
|
||||
@@ -49,7 +49,7 @@ export function LoginPage() {
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="error">{error}</p>}
|
||||
<button className="login-cta" disabled={loading}>{loading ? "Bitte warten..." : "Einloggen"}</button>
|
||||
<button className="login-cta" disabled={loading}>{loading ? "Please wait..." : "Sign in"}</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ export function QueryInsightsPage() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Query Insights</h2>
|
||||
<p>Hinweis: Benötigt aktivierte Extension <code>pg_stat_statements</code> auf dem Zielsystem.</p>
|
||||
<p>Note: This section requires the <code>pg_stat_statements</code> extension on the monitored target.</p>
|
||||
{error && <div className="card error">{error}</div>}
|
||||
<div className="card">
|
||||
<label>Target </label>
|
||||
|
||||
@@ -135,7 +135,7 @@ export function TargetDetailPage() {
|
||||
[series]
|
||||
);
|
||||
|
||||
if (loading) return <div className="card">Lade Target Detail...</div>;
|
||||
if (loading) return <div className="card">Loading target detail...</div>;
|
||||
if (error) return <div className="card error">{error}</div>;
|
||||
|
||||
const role = overview?.instance?.role || "-";
|
||||
@@ -157,7 +157,7 @@ export function TargetDetailPage() {
|
||||
<span>Role</span>
|
||||
<strong className={isPrimary ? "pill primary" : isStandby ? "pill standby" : "pill"}>{role}</strong>
|
||||
</div>
|
||||
<div title="Zeit seit Start des Postgres-Prozesses">
|
||||
<div title="Time since PostgreSQL postmaster start">
|
||||
<span>Uptime</span><strong>{formatSeconds(overview.instance.uptime_seconds)}</strong>
|
||||
</div>
|
||||
<div><span>Database</span><strong>{overview.instance.current_database || "-"}</strong></div>
|
||||
@@ -165,16 +165,16 @@ export function TargetDetailPage() {
|
||||
<span>Target Port</span>
|
||||
<strong>{targetMeta?.port ?? "-"}</strong>
|
||||
</div>
|
||||
<div title="Groesse der aktuell verbundenen Datenbank">
|
||||
<div title="Current database total size">
|
||||
<span>Current DB Size</span><strong>{formatBytes(overview.storage.current_database_size_bytes)}</strong>
|
||||
</div>
|
||||
<div title="Gesamtgroesse der WAL-Dateien (falls verfuegbar)">
|
||||
<div title="Total WAL directory size (when available)">
|
||||
<span>WAL Size</span><strong>{formatBytes(overview.storage.wal_directory_size_bytes)}</strong>
|
||||
</div>
|
||||
<div title="Optional ueber Agent/SSH ermittelbar">
|
||||
<div title="Optional metric via future Agent/SSH provider">
|
||||
<span>Free Disk</span><strong>{formatBytes(overview.storage.disk_space.free_bytes)}</strong>
|
||||
</div>
|
||||
<div title="Zeitliche Replikationsverzoegerung auf Standby">
|
||||
<div title="Replication replay delay on standby">
|
||||
<span>Replay Lag</span>
|
||||
<strong className={overview.replication.replay_lag_seconds > 5 ? "lag-bad" : ""}>
|
||||
{formatSeconds(overview.replication.replay_lag_seconds)}
|
||||
@@ -262,7 +262,7 @@ export function TargetDetailPage() {
|
||||
))}
|
||||
</div>
|
||||
<div className="card" style={{ height: 320 }}>
|
||||
<h3>Connections / TPS approx / Cache hit ratio</h3>
|
||||
<h3>Connections / TPS (approx) / Cache Hit Ratio</h3>
|
||||
<ResponsiveContainer width="100%" height="85%">
|
||||
<LineChart data={chartData}>
|
||||
<XAxis dataKey="ts" hide />
|
||||
|
||||
@@ -51,7 +51,7 @@ export function TargetsPage() {
|
||||
};
|
||||
|
||||
const deleteTarget = async (id) => {
|
||||
if (!confirm("Target loeschen?")) return;
|
||||
if (!confirm("Delete target?")) return;
|
||||
try {
|
||||
await apiFetch(`/targets/${id}`, { method: "DELETE" }, tokens, refresh);
|
||||
await load();
|
||||
@@ -69,42 +69,42 @@ export function TargetsPage() {
|
||||
<details className="card collapsible" open>
|
||||
<summary className="collapse-head">
|
||||
<div>
|
||||
<h3>Neues Target</h3>
|
||||
<p>Verbindungsdaten fuer eine PostgreSQL-Instanz.</p>
|
||||
<h3>New Target</h3>
|
||||
<p>Connection settings for a PostgreSQL instance.</p>
|
||||
</div>
|
||||
<span className="collapse-chevron" aria-hidden="true">?</span>
|
||||
<span className="collapse-chevron" aria-hidden="true">v</span>
|
||||
</summary>
|
||||
|
||||
<form className="target-form grid two" onSubmit={createTarget}>
|
||||
<div className="field">
|
||||
<label>Name</label>
|
||||
<input placeholder="z.B. Prod-DB" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} required />
|
||||
<small>Eindeutiger Anzeigename im Dashboard.</small>
|
||||
<input placeholder="e.g. Prod-DB" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} required />
|
||||
<small>Unique display name in the dashboard.</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Host</label>
|
||||
<input placeholder="z.B. 172.16.0.106 oder db.internal" value={form.host} onChange={(e) => setForm({ ...form, host: e.target.value })} required />
|
||||
<small>Wichtig: Muss vom Backend-Container aus erreichbar sein.</small>
|
||||
<input placeholder="e.g. 172.16.0.106 or db.internal" value={form.host} onChange={(e) => setForm({ ...form, host: e.target.value })} required />
|
||||
<small>Must be reachable from the backend container.</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Port</label>
|
||||
<input placeholder="5432" value={form.port} onChange={(e) => setForm({ ...form, port: Number(e.target.value) })} type="number" required />
|
||||
<small>Standard PostgreSQL Port ist 5432 (oder gemappter Host-Port).</small>
|
||||
<small>Default PostgreSQL port is 5432 (or your mapped host port).</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>DB Name</label>
|
||||
<input placeholder="z.B. postgres oder appdb" value={form.dbname} onChange={(e) => setForm({ ...form, dbname: e.target.value })} required />
|
||||
<small>Name der Datenbank, die ueberwacht werden soll.</small>
|
||||
<input placeholder="e.g. postgres or appdb" value={form.dbname} onChange={(e) => setForm({ ...form, dbname: e.target.value })} required />
|
||||
<small>Database name to monitor.</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Username</label>
|
||||
<input placeholder="z.B. postgres" value={form.username} onChange={(e) => setForm({ ...form, username: e.target.value })} required />
|
||||
<small>DB User mit Leserechten auf Stats-Views.</small>
|
||||
<input placeholder="e.g. postgres" value={form.username} onChange={(e) => setForm({ ...form, username: e.target.value })} required />
|
||||
<small>DB user with read permissions on stats views.</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Password</label>
|
||||
<input placeholder="Passwort" type="password" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} required />
|
||||
<small>Wird verschluesselt in der Core-DB gespeichert.</small>
|
||||
<input placeholder="Password" type="password" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} required />
|
||||
<small>Stored encrypted in the core database.</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>SSL Mode</label>
|
||||
@@ -114,12 +114,12 @@ export function TargetsPage() {
|
||||
<option value="require">require</option>
|
||||
</select>
|
||||
<small>
|
||||
Bei Fehler "rejected SSL upgrade" auf <code>disable</code> stellen.
|
||||
If you see "rejected SSL upgrade", switch to <code>disable</code>.
|
||||
</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label> </label>
|
||||
<button className="primary-btn">Target anlegen</button>
|
||||
<button className="primary-btn">Create target</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
@@ -130,25 +130,25 @@ export function TargetsPage() {
|
||||
<summary className="collapse-head">
|
||||
<div>
|
||||
<h3>Troubleshooting</h3>
|
||||
<p>Typische Verbindungsfehler schnell erkennen.</p>
|
||||
<p>Quick checks for the most common connection issues.</p>
|
||||
</div>
|
||||
<span className="collapse-chevron" aria-hidden="true">?</span>
|
||||
<span className="collapse-chevron" aria-hidden="true">v</span>
|
||||
</summary>
|
||||
<p>
|
||||
<code>Connection refused</code>: Host/Port falsch oder DB nicht erreichbar.
|
||||
<code>Connection refused</code>: host/port is wrong or database is unreachable.
|
||||
</p>
|
||||
<p>
|
||||
<code>rejected SSL upgrade</code>: SSL Mode auf <code>disable</code> setzen.
|
||||
<code>rejected SSL upgrade</code>: set SSL mode to <code>disable</code>.
|
||||
</p>
|
||||
<p>
|
||||
<code>localhost</code> im Target zeigt aus Backend-Container-Sicht auf den Container selbst.
|
||||
<code>localhost</code> points to the backend container itself, not your host machine.
|
||||
</p>
|
||||
</details>
|
||||
)}
|
||||
|
||||
<div className="card targets-table">
|
||||
{loading ? (
|
||||
<p>Lade Targets...</p>
|
||||
<p>Loading targets...</p>
|
||||
) : (
|
||||
<table>
|
||||
<thead>
|
||||
@@ -156,7 +156,7 @@ export function TargetsPage() {
|
||||
<th>Name</th>
|
||||
<th>Host</th>
|
||||
<th>DB</th>
|
||||
<th>Aktionen</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
Reference in New Issue
Block a user