Gruppen-Bearbeiten als Modal, Mobile-Fix, Mehrfach-Upload (v1.1.3)
- Gruppen bearbeiten (Name + Rechte) laeuft jetzt ueber ein Modal statt der bisherigen Inline-Ausklapp-Zeile -- gleiche Optik wie "Neue Gruppe". Der Gruppenname war dabei bisher ein verstecktes, nie wirklich editierbares Feld; jetzt ein normales Texteingabefeld (Backend unterstuetzte das Umbenennen inkl. Systemgruppen-Schutz bereits vollstaendig, es fehlte nur die Eingabemoeglichkeit im Formular). Admin-Rechte-Ansicht und die "Freischalten"-Ausnahme fuer die Systemgruppe "Benutzer" ziehen ins jeweilige Modal mit um. - Fileshare-Baum + Tabelle nebeneinander sprengte auf Tablet-/Handybreite die Seite -- stapelt jetzt (Baum oben, Tabelle darunter) ab der bestehenden 900px-Sidebar-Umschaltgrenze. - Mehrfach-Upload: Datei-Eingabefeld erlaubt jetzt echte Mehrfachauswahl (mehrere Dateien in einem Dialog) UND mehrmaliges Hinzufuegen nacheinander (per DataTransfer angesammelt, bevor "Hochladen" gedrueckt wird) -- funktioniert nativ auch auf Mobilgeraeten, da kein Custom- Upload-Mechanismus noetig ist. Backend verarbeitet jetzt eine Liste statt einer einzelnen Datei (request.files.getlist), mit Sammel- Erfolgsmeldung und pro Datei separater Namensvalidierung. Live auf POETEST verifiziert: Umbenennen+Rechte-Speichern ueber das neue Modal, Admin-Modal (readonly), Mobile-Layout (390px, Baum stapelt korrekt), echte Mehrfachauswahl (2 Dateien in einem Dialog, beide korrekt hochgeladen und einzeln in der Sammelmeldung genannt). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -140,14 +140,15 @@
|
||||
<input type="hidden" name="share" value="{{ selected_share }}">
|
||||
<input type="hidden" name="path" value="{{ rel_path }}">
|
||||
<div class="modal-header">
|
||||
<h3>Datei hochladen</h3>
|
||||
<h3>Datei(en) hochladen</h3>
|
||||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="field">
|
||||
<label>Datei</label>
|
||||
<input type="file" name="file" required>
|
||||
<div class="field-hint">Maximal 15 MB pro Datei.</div>
|
||||
<label>Datei(en)</label>
|
||||
<input type="file" name="file" id="uploadFileInput" multiple required>
|
||||
<div id="uploadFileList" class="upload-file-list"></div>
|
||||
<div class="field-hint">Insgesamt maximal 15 MB pro Upload-Vorgang. Mehrfachauswahl möglich (auch mehrmals nacheinander — bereits hinzugefügte Dateien bleiben dabei erhalten).</div>
|
||||
</div>
|
||||
<div class="field-hint">Wird in „{{ selected_share }}{% if rel_path %} / {{ rel_path }}{% endif %}“ hochgeladen. Eine bereits vorhandene Datei gleichen Namens wird überschrieben.</div>
|
||||
</div>
|
||||
@@ -242,6 +243,70 @@ function filterTable(inputId, tableId) {
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------------- Mehrfach-Upload (Multiauswahl + mehrmals nacheinander) ---------------- */
|
||||
/* Ein <input type=file multiple> ERSETZT bei jeder erneuten Dateiauswahl
|
||||
die vorherige -- fuer "mehrmals nacheinander hinzufuegen" wird deshalb
|
||||
selbst eine "angesammelte" Auswahl per DataTransfer gepflegt und nach
|
||||
jeder Aenderung zurueck auf das Input-Feld geschrieben, sodass das
|
||||
normale <form>-Submit (kein fetch() noetig) am Ende alle gesammelten
|
||||
Dateien mitschickt. DataTransfer-Zuweisung an .files wird von allen
|
||||
gaengigen Mobil-Browsern (Android Chrome, iOS Safari) mitgetragen; falls
|
||||
nicht, faellt es einfach auf das native Verhalten (letzte Auswahl zaehlt)
|
||||
zurueck, ohne den Upload an sich zu verhindern. */
|
||||
(function () {
|
||||
const input = document.getElementById("uploadFileInput");
|
||||
const listEl = document.getElementById("uploadFileList");
|
||||
if (!input || !listEl) return;
|
||||
let staged = null;
|
||||
try { staged = new DataTransfer(); } catch (e) { staged = null; }
|
||||
|
||||
function render() {
|
||||
listEl.innerHTML = "";
|
||||
if (!staged) return;
|
||||
Array.from(staged.files).forEach(function (file, idx) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "upload-file-row";
|
||||
const name = document.createElement("span");
|
||||
name.textContent = file.name;
|
||||
const removeBtn = document.createElement("button");
|
||||
removeBtn.type = "button";
|
||||
removeBtn.className = "upload-file-remove";
|
||||
removeBtn.setAttribute("aria-label", "Entfernen");
|
||||
removeBtn.textContent = "×";
|
||||
removeBtn.addEventListener("click", function () {
|
||||
const dt = new DataTransfer();
|
||||
Array.from(staged.files).forEach(function (f, i) {
|
||||
if (i !== idx) dt.items.add(f);
|
||||
});
|
||||
staged = dt;
|
||||
input.files = staged.files;
|
||||
render();
|
||||
});
|
||||
row.appendChild(name);
|
||||
row.appendChild(removeBtn);
|
||||
listEl.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
input.addEventListener("change", function () {
|
||||
if (!staged) return; // kein DataTransfer-Support -- natives Verhalten greift
|
||||
Array.from(input.files).forEach(function (file) { staged.items.add(file); });
|
||||
input.files = staged.files;
|
||||
render();
|
||||
});
|
||||
|
||||
// Beim (Wieder-)Oeffnen des Modals eine frische Sammlung starten, statt
|
||||
// Dateien aus einem vorherigen, bereits abgeschickten Upload-Vorgang
|
||||
// versehentlich mitzuschleppen.
|
||||
document.querySelectorAll('[data-open-modal="uploadModal"]').forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
try { staged = new DataTransfer(); } catch (e) { staged = null; }
|
||||
input.value = "";
|
||||
render();
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
/* ---------------- Baum-Navigation (Freigaben links) ---------------- */
|
||||
|
||||
function buildTreeNode(share, path, name) {
|
||||
|
||||
@@ -104,8 +104,8 @@
|
||||
<td class="text-dim">{{ admin_virtual_group.member_names|length }}</td>
|
||||
<td>
|
||||
<div class="row-actions">
|
||||
<button class="icon-btn" title="Rechte anzeigen" onclick="toggleDetail('detail-admin')">
|
||||
<svg id="chev-admin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
||||
<button class="icon-btn" title="Rechte anzeigen" data-open-modal="adminGroupModal">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" title="Mitglieder verwalten" data-open-modal="adminMembersModal">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.2"/><path d="M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6"/><circle cx="17.5" cy="8.5" r="2.4"/><path d="M15.8 14.2c2.7.3 4.7 2.4 4.7 5.3"/></svg>
|
||||
@@ -113,16 +113,11 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="group-detail-row hidden" id="detail-admin">
|
||||
<td colspan="3">
|
||||
{{ permission_tree(admin_virtual_group.permissions, true, true) }}
|
||||
<p class="text-faint" style="font-size:11.5px; margin:12px 0 0;">Admins dürfen immer alles — diese Rechte sind fest und nicht änderbar.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
{% for g in groups %}
|
||||
{% set can_edit_this = current_user.has_permission('groups.edit') and not g.is_system %}
|
||||
{% set can_unlock_system = g.is_system and current_user.is_admin %}
|
||||
<tbody data-sort-name="{{ g.name|lower }}" data-sort-members="{{ g.member_names|length }}">
|
||||
<tr>
|
||||
<td class="cell-name">
|
||||
@@ -132,8 +127,12 @@
|
||||
<td class="text-dim">{{ g.member_names|length }}</td>
|
||||
<td>
|
||||
<div class="row-actions">
|
||||
<button class="icon-btn" title="Rechte anzeigen{{ '/bearbeiten' if can_edit_this else '' }}" onclick="toggleDetail('detail-{{ g.id }}')">
|
||||
<svg id="chev-{{ g.id }}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
||||
<button class="icon-btn" title="{{ 'Bearbeiten' if (can_edit_this or can_unlock_system) else 'Anzeigen' }}" data-open-modal="editGroupModal{{ loop.index }}">
|
||||
{% if can_edit_this or can_unlock_system %}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
{% else %}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
{% endif %}
|
||||
</button>
|
||||
<button class="icon-btn" title="Mitglieder verwalten" data-open-modal="membersModal{{ loop.index }}">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.2"/><path d="M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6"/><circle cx="17.5" cy="8.5" r="2.4"/><path d="M15.8 14.2c2.7.3 4.7 2.4 4.7 5.3"/></svg>
|
||||
@@ -149,57 +148,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% set can_unlock_system = g.is_system and current_user.is_admin %}
|
||||
<tr class="group-detail-row hidden" id="detail-{{ g.id }}">
|
||||
<td colspan="3">
|
||||
{% if can_edit_this %}
|
||||
<form method="post">
|
||||
<input type="hidden" name="save_group" value="1">
|
||||
<input type="hidden" name="permissions_submitted" value="1">
|
||||
<input type="hidden" name="group_id" value="{{ g.id }}">
|
||||
<input type="hidden" name="name" value="{{ g.name }}">
|
||||
{{ permission_tree(g.permissions, false, true) }}
|
||||
<div class="flex" style="justify-content:flex-end; margin-top:16px;">
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
Rechte speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% elif can_unlock_system %}
|
||||
<div id="readonly-{{ g.id }}">
|
||||
{{ permission_tree(g.permissions, true, true) }}
|
||||
<div class="flex" style="justify-content:space-between; align-items:center; margin-top:12px;">
|
||||
<p class="text-faint" style="font-size:11.5px; margin:0;">Die Standardgruppe „Benutzer“ ist eine Systemgruppe — ihre Rechte sind normalerweise fest.</p>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="unlockSystemGroup({{ g.id }})">Freischalten</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" class="hidden" id="unlock-{{ g.id }}">
|
||||
<input type="hidden" name="save_group" value="1">
|
||||
<input type="hidden" name="permissions_submitted" value="1">
|
||||
<input type="hidden" name="unlock_system_group" value="1">
|
||||
<input type="hidden" name="group_id" value="{{ g.id }}">
|
||||
<input type="hidden" name="name" value="{{ g.name }}">
|
||||
{{ permission_tree(g.permissions, false, true) }}
|
||||
<p class="text-faint" style="font-size:11.5px; margin:12px 0;">
|
||||
⚠ Diese Gruppe ist die Standardgruppe für neue Benutzer (auch neu angelegte AD/LDAP-Konten). Zu restriktive
|
||||
Rechte hier können den Erst-Login neuer Konten einschränken.
|
||||
</p>
|
||||
<div class="flex" style="justify-content:flex-end;">
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
Rechte speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
{{ permission_tree(g.permissions, true, true) }}
|
||||
{% if g.is_system %}
|
||||
<p class="text-faint" style="font-size:11.5px; margin:12px 0 0;">Die Standardgruppe „Benutzer“ ist eine Systemgruppe — ihre Rechte sind fest und nicht änderbar.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{% else %}
|
||||
<tbody data-sort-pinned>
|
||||
@@ -210,6 +158,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="adminGroupModal">
|
||||
<div class="modal" style="max-width:1000px;">
|
||||
<div class="modal-header">
|
||||
<h3>Admin — Rechte</h3>
|
||||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ permission_tree(admin_virtual_group.permissions, true, true) }}
|
||||
<p class="text-faint" style="font-size:11.5px; margin:12px 0 0;">Admins dürfen immer alles — diese Rechte sind fest und nicht änderbar.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="adminMembersModal">
|
||||
<div class="modal" style="max-width:380px;">
|
||||
<form method="post">
|
||||
@@ -238,6 +199,8 @@
|
||||
</div>
|
||||
|
||||
{% for g in groups %}
|
||||
{% set can_edit_this = current_user.has_permission('groups.edit') and not g.is_system %}
|
||||
{% set can_unlock_system = g.is_system and current_user.is_admin %}
|
||||
<div class="modal-overlay" id="membersModal{{ loop.index }}">
|
||||
<div class="modal" style="max-width:380px;">
|
||||
<form method="post">
|
||||
@@ -268,6 +231,80 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="editGroupModal{{ loop.index }}">
|
||||
<div class="modal" style="max-width:1000px;">
|
||||
{% if can_edit_this %}
|
||||
<form method="post">
|
||||
<input type="hidden" name="save_group" value="1">
|
||||
<input type="hidden" name="permissions_submitted" value="1">
|
||||
<input type="hidden" name="group_id" value="{{ g.id }}">
|
||||
<div class="modal-header">
|
||||
<h3>Gruppe bearbeiten</h3>
|
||||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input type="text" name="name" value="{{ g.name }}" required>
|
||||
</div>
|
||||
{{ permission_tree(g.permissions, false, true) }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-close-modal>Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% elif can_unlock_system %}
|
||||
<div class="modal-header">
|
||||
<h3>Gruppe „{{ g.name }}“</h3>
|
||||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="field"><label>Name</label><input type="text" value="{{ g.name }}" disabled></div>
|
||||
<div id="readonly-{{ g.id }}">
|
||||
{{ permission_tree(g.permissions, true, true) }}
|
||||
<div class="flex" style="justify-content:space-between; align-items:center; margin-top:12px; flex-wrap:wrap;">
|
||||
<p class="text-faint" style="font-size:11.5px; margin:0;">Die Standardgruppe „Benutzer“ ist eine Systemgruppe — ihre Rechte sind normalerweise fest.</p>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="unlockSystemGroup({{ g.id }})">Freischalten</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" class="hidden" id="unlock-{{ g.id }}">
|
||||
<input type="hidden" name="save_group" value="1">
|
||||
<input type="hidden" name="permissions_submitted" value="1">
|
||||
<input type="hidden" name="unlock_system_group" value="1">
|
||||
<input type="hidden" name="group_id" value="{{ g.id }}">
|
||||
<input type="hidden" name="name" value="{{ g.name }}">
|
||||
{{ permission_tree(g.permissions, false, true) }}
|
||||
<p class="text-faint" style="font-size:11.5px; margin:12px 0;">
|
||||
⚠ Diese Gruppe ist die Standardgruppe für neue Benutzer (auch neu angelegte AD/LDAP-Konten). Zu restriktive
|
||||
Rechte hier können den Erst-Login neuer Konten einschränken.
|
||||
</p>
|
||||
<div class="flex" style="justify-content:flex-end;">
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
Rechte speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="modal-header">
|
||||
<h3>Gruppe „{{ g.name }}“</h3>
|
||||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ permission_tree(g.permissions, true, true) }}
|
||||
{% if g.is_system %}
|
||||
<p class="text-faint" style="font-size:11.5px; margin:12px 0 0;">Die Standardgruppe „Benutzer“ ist eine Systemgruppe — ihre Rechte sind fest und nicht änderbar.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="modal-overlay" id="addGroupModal">
|
||||
@@ -317,14 +354,6 @@ function unlockSystemGroup(id) {
|
||||
);
|
||||
}
|
||||
|
||||
function toggleDetail(id) {
|
||||
const row = document.getElementById(id);
|
||||
if (!row) return;
|
||||
row.classList.toggle("hidden");
|
||||
const chev = document.getElementById(id.replace("detail-", "chev-"));
|
||||
if (chev) chev.style.transform = row.classList.contains("hidden") ? "" : "rotate(180deg)";
|
||||
}
|
||||
|
||||
function applyPermissionGating() {
|
||||
document.querySelectorAll(".permission-group-col").forEach(function (area) {
|
||||
const toggle = area.querySelector(".permission-area-toggle-cb");
|
||||
|
||||
Reference in New Issue
Block a user