Refactor form structure and add collapsible components
Improved the user interface on the TargetsPage by replacing static form headers with collapsible sections, enhancing maintainability and user experience. Updated styles for consistency, added hover effects, and ensured accessibility. Also replaced German special characters for uniform encoding.
This commit is contained in:
@@ -51,7 +51,7 @@ export function TargetsPage() {
|
||||
};
|
||||
|
||||
const deleteTarget = async (id) => {
|
||||
if (!confirm("Target löschen?")) return;
|
||||
if (!confirm("Target loeschen?")) return;
|
||||
try {
|
||||
await apiFetch(`/targets/${id}`, { method: "DELETE" }, tokens, refresh);
|
||||
await load();
|
||||
@@ -64,62 +64,76 @@ export function TargetsPage() {
|
||||
<div className="targets-page">
|
||||
<h2>Targets Management</h2>
|
||||
{error && <div className="card error">{error}</div>}
|
||||
|
||||
{canManage && (
|
||||
<form className="card target-form grid two" onSubmit={createTarget}>
|
||||
<div className="target-form-header">
|
||||
<h3>Neues Target</h3>
|
||||
<p>Verbindungsdaten fuer eine PostgreSQL-Instanz.</p>
|
||||
</div>
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</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 überwacht werden soll.</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>
|
||||
</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 verschlüsselt in der Core-DB gespeichert.</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>SSL Mode</label>
|
||||
<select value={form.sslmode} onChange={(e) => setForm({ ...form, sslmode: e.target.value })}>
|
||||
<option value="disable">disable</option>
|
||||
<option value="prefer">prefer</option>
|
||||
<option value="require">require</option>
|
||||
</select>
|
||||
<small>
|
||||
Bei Fehler "rejected SSL upgrade" auf <code>disable</code> stellen.
|
||||
</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label> </label>
|
||||
<button className="primary-btn">Target anlegen</button>
|
||||
</div>
|
||||
</form>
|
||||
<details className="card collapsible" open>
|
||||
<summary className="collapse-head">
|
||||
<div>
|
||||
<h3>Neues Target</h3>
|
||||
<p>Verbindungsdaten fuer eine PostgreSQL-Instanz.</p>
|
||||
</div>
|
||||
<span className="collapse-chevron" aria-hidden="true">?</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>SSL Mode</label>
|
||||
<select value={form.sslmode} onChange={(e) => setForm({ ...form, sslmode: e.target.value })}>
|
||||
<option value="disable">disable</option>
|
||||
<option value="prefer">prefer</option>
|
||||
<option value="require">require</option>
|
||||
</select>
|
||||
<small>
|
||||
Bei Fehler "rejected SSL upgrade" auf <code>disable</code> stellen.
|
||||
</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label> </label>
|
||||
<button className="primary-btn">Target anlegen</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
)}
|
||||
|
||||
{canManage && (
|
||||
<div className="card tips">
|
||||
<strong>Troubleshooting</strong>
|
||||
<details className="card collapsible tips">
|
||||
<summary className="collapse-head">
|
||||
<div>
|
||||
<h3>Troubleshooting</h3>
|
||||
<p>Typische Verbindungsfehler schnell erkennen.</p>
|
||||
</div>
|
||||
<span className="collapse-chevron" aria-hidden="true">?</span>
|
||||
</summary>
|
||||
<p>
|
||||
<code>Connection refused</code>: Host/Port falsch oder DB nicht erreichbar.
|
||||
</p>
|
||||
@@ -129,8 +143,9 @@ export function TargetsPage() {
|
||||
<p>
|
||||
<code>localhost</code> im Target zeigt aus Backend-Container-Sicht auf den Container selbst.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
|
||||
<div className="card targets-table">
|
||||
{loading ? (
|
||||
<p>Lade Targets...</p>
|
||||
|
||||
@@ -195,36 +195,59 @@ button {
|
||||
}
|
||||
|
||||
.target-form {
|
||||
position: relative;
|
||||
padding-top: 56px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.target-form-header {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 16px;
|
||||
.primary-btn {
|
||||
font-weight: 650;
|
||||
letter-spacing: 0.01em;
|
||||
border-color: #4467ab;
|
||||
background: linear-gradient(180deg, #1a2f56, #142643);
|
||||
box-shadow: inset 0 1px 0 #5f8de144;
|
||||
}
|
||||
|
||||
.target-form-header h3 {
|
||||
.primary-btn:hover {
|
||||
border-color: #6b9ee9;
|
||||
background: linear-gradient(180deg, #1d3766, #183053);
|
||||
}
|
||||
|
||||
.collapsible {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.collapse-head {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: -2px 0 0 0;
|
||||
padding: 0 2px 8px 2px;
|
||||
}
|
||||
|
||||
.collapse-head::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse-head h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.target-form-header p {
|
||||
.collapse-head p {
|
||||
margin: 2px 0 0 0;
|
||||
color: #92a7cc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
font-weight: 700;
|
||||
border-color: #3e73d4;
|
||||
background: linear-gradient(90deg, #2e7cd4, #265fb4);
|
||||
.collapse-chevron {
|
||||
font-size: 22px;
|
||||
color: #89a7d8;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.primary-btn:hover {
|
||||
border-color: #6aa8ff;
|
||||
filter: brightness(1.05);
|
||||
details[open] .collapse-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.targets-table table tbody tr:hover {
|
||||
|
||||
Reference in New Issue
Block a user