Promote develop → main (2026-07-25 22:22 UTC) #16
@@ -177,40 +177,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bereich: Einstellungen -->
|
||||
<div class="org-block" id="sub-settings">
|
||||
<h2 class="org-block-title">Einstellungen</h2>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form id="orgEditForm">
|
||||
<div class="form-group">
|
||||
<label for="editOrgName">Name</label>
|
||||
<input type="text" id="editOrgName">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status</label>
|
||||
<select id="editOrgActive">
|
||||
<option value="true">Aktiv</option>
|
||||
<option value="false">Deaktiviert</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="editOrgLanguage">Pipeline-Sprache</label>
|
||||
<select id="editOrgLanguage">
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
</select>
|
||||
<small class="text-secondary">Bestimmt die Ausgabesprache der KI (Lagebild, Faktencheck, Recherche) und der sichtbarsten UI-Elemente fuer alle Nutzer dieser Organisation.</small>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; margin-top: 16px;">
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
<button type="button" class="btn btn-danger" id="deleteOrgBtn">Organisation löschen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Der fruehere Einstellungen-Block ist aufgeloest. Status und
|
||||
Pipeline-Sprache sind direkt in der Lizenz-Tabelle umschaltbar,
|
||||
Umbenennen und Loeschen sitzen in der Kopfzeile. -->
|
||||
<div class="org-block" id="sub-tokens">
|
||||
<h2 class="org-block-title">Verbrauch & Kontingent</h2>
|
||||
<div class="token-overview">
|
||||
@@ -261,6 +230,8 @@
|
||||
<th>Gültig ab</th>
|
||||
<th>Gültig bis</th>
|
||||
<th>Status</th>
|
||||
<th>Org-Status</th>
|
||||
<th>Pipeline-Sprache</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -776,6 +747,29 @@
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modal: Organisation umbenennen -->
|
||||
<div class="modal-overlay" id="modalRenameOrg">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3>Organisation umbenennen</h3>
|
||||
<button class="modal-close" onclick="closeModal('modalRenameOrg')">×</button>
|
||||
</div>
|
||||
<form id="renameOrgForm">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="renameOrgName">Name</label>
|
||||
<input type="text" id="renameOrgName" required maxlength="200">
|
||||
</div>
|
||||
<div id="renameOrgError" class="error-msg" style="display:none"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeModal('modalRenameOrg')">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: Lizenz verlängern (ersetzt das alte Browser-prompt) -->
|
||||
<div class="modal-overlay" id="modalExtendLicense">
|
||||
<div class="modal">
|
||||
@@ -1169,7 +1163,7 @@
|
||||
</div>
|
||||
|
||||
<script src="/static/js/a11y.js?v=20260725a"></script>
|
||||
<script src="/static/js/app.js?v=20260725g"></script>
|
||||
<script src="/static/js/app.js?v=20260725h"></script>
|
||||
<script src="/static/js/sources.js?v=20260522x2"></script>
|
||||
<script src="/static/js/x-scraper.js?v=20260522a"></script>
|
||||
<script src="/static/js/source-health.js?v=20260509l"></script>
|
||||
|
||||
@@ -306,6 +306,29 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
// --- Open Org Detail ---
|
||||
let currentOrgData = null; // zuletzt geladene Org (fuer Inline-Einstellungen)
|
||||
|
||||
function renderOrgHeader(org) {
|
||||
const kpiAlt = document.getElementById("orgKpiCredits");
|
||||
const kpiText = kpiAlt ? kpiAlt.textContent : "wird geladen …";
|
||||
document.getElementById("orgDetailHeader").innerHTML = `
|
||||
<div style="display:flex; align-items:flex-start; gap:10px; flex-wrap:wrap;">
|
||||
<div style="flex:1; min-width:260px;">
|
||||
<h2>${esc(org.name)} <span class="badge badge-${org.is_active ? 'active' : 'inactive'}" style="font-size: 12px; vertical-align: middle;">${org.is_active ? "Aktiv" : "Inaktiv"}</span></h2>
|
||||
<div class="text-secondary mt-8">Slug: ${esc(org.slug)} | Nutzer: ${org.user_count} | Lizenz: ${org.license_type || "Keine"} | Kontingent: <span id="orgKpiCredits">${esc(kpiText)}</span></div>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-small" onclick="renameOrg()" title="Organisation umbenennen">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
|
||||
Umbenennen
|
||||
</button>
|
||||
<button class="btn btn-danger btn-small" onclick="confirmDeleteOrg()" title="Organisation endgültig löschen">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function openOrg(orgId) {
|
||||
currentOrgId = orgId;
|
||||
document.getElementById("orgListView").style.display = "none";
|
||||
@@ -315,15 +338,8 @@ async function openOrg(orgId) {
|
||||
|
||||
try {
|
||||
const org = await API.get(`/api/orgs/${orgId}`);
|
||||
document.getElementById("orgDetailHeader").innerHTML = `
|
||||
<h2>${esc(org.name)} <span class="badge badge-${org.is_active ? 'active' : 'inactive'}" style="font-size: 12px; vertical-align: middle;">${org.is_active ? "Aktiv" : "Inaktiv"}</span></h2>
|
||||
<div class="text-secondary mt-8">Slug: ${esc(org.slug)} | Nutzer: ${org.user_count} | Lizenz: ${org.license_type || "Keine"} | Kontingent: <span id="orgKpiCredits">wird geladen …</span></div>
|
||||
`;
|
||||
|
||||
document.getElementById("editOrgName").value = org.name;
|
||||
document.getElementById("editOrgActive").value = org.is_active ? "true" : "false";
|
||||
const langEl = document.getElementById("editOrgLanguage");
|
||||
if (langEl) langEl.value = org.output_language || "de";
|
||||
currentOrgData = org;
|
||||
renderOrgHeader(org);
|
||||
|
||||
// Alle Bereiche sofort laden, es gibt keine Unterreiter mehr
|
||||
loadOrgUsers(orgId);
|
||||
@@ -334,6 +350,60 @@ async function openOrg(orgId) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Inline-Einstellungen (ersetzen den frueheren Einstellungen-Block) ---
|
||||
|
||||
async function _saveOrgSetting(patch, meldung) {
|
||||
try {
|
||||
await API.put(`/api/orgs/${currentOrgId}`, patch);
|
||||
currentOrgData = await API.get(`/api/orgs/${currentOrgId}`);
|
||||
renderOrgHeader(currentOrgData);
|
||||
loadOrgLicenses(currentOrgId);
|
||||
loadOrgs();
|
||||
showToast(meldung, "success");
|
||||
} catch (err) {
|
||||
showToast(err.message, "error");
|
||||
loadOrgLicenses(currentOrgId); // Auswahl auf den echten Stand zuruecksetzen
|
||||
}
|
||||
}
|
||||
|
||||
function setOrgActive(wert) {
|
||||
_saveOrgSetting({ is_active: wert === "true" },
|
||||
wert === "true" ? "Organisation aktiviert" : "Organisation deaktiviert");
|
||||
}
|
||||
|
||||
function setOrgLanguage(wert) {
|
||||
_saveOrgSetting({ output_language: wert }, "Pipeline-Sprache gespeichert");
|
||||
}
|
||||
|
||||
function renameOrg() {
|
||||
const input = document.getElementById("renameOrgName");
|
||||
const errEl = document.getElementById("renameOrgError");
|
||||
if (errEl) errEl.style.display = "none";
|
||||
if (input && currentOrgData) input.value = currentOrgData.name;
|
||||
openModal("modalRenameOrg");
|
||||
if (input) input.focus();
|
||||
}
|
||||
|
||||
function confirmDeleteOrg() {
|
||||
showConfirm(
|
||||
"Organisation löschen",
|
||||
"Soll die Organisation mit allen Nutzern und Lizenzen endgültig gelöscht werden? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
async () => {
|
||||
try {
|
||||
await API.del(`/api/orgs/${currentOrgId}`);
|
||||
document.getElementById("orgListView").style.display = "";
|
||||
document.getElementById("orgDetail").classList.remove("active");
|
||||
currentOrgId = null;
|
||||
currentOrgData = null;
|
||||
loadOrgs();
|
||||
loadDashboard();
|
||||
} catch (err) {
|
||||
showToast(err.message, "error");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// --- Org Users ---
|
||||
async function loadOrgUsers(orgId) {
|
||||
try {
|
||||
@@ -436,9 +506,21 @@ async function loadOrgLicenses(orgId) {
|
||||
const licenses = await API.get(`/api/licenses?org_id=${orgId}`);
|
||||
const tbody = document.getElementById("licenseTable");
|
||||
if (licenses.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-muted">Keine Lizenzen</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="text-muted">Keine Lizenzen</td></tr>';
|
||||
return;
|
||||
}
|
||||
// Org-Status und Pipeline-Sprache sind Org-Einstellungen und stehen nur in
|
||||
// der aktiven Zeile, direkt umschaltbar (Sofort-Speicherung).
|
||||
const org = currentOrgData || {};
|
||||
const orgZellen = `
|
||||
<td><select style="width:auto; min-width:110px;" onchange="setOrgActive(this.value)">
|
||||
<option value="true"${org.is_active ? " selected" : ""}>Aktiv</option>
|
||||
<option value="false"${org.is_active ? "" : " selected"}>Deaktiviert</option>
|
||||
</select></td>
|
||||
<td><select style="width:auto; min-width:100px;" onchange="setOrgLanguage(this.value)" title="Ausgabesprache der KI-Pipeline für alle Nutzer dieser Organisation">
|
||||
<option value="de"${(org.output_language || "de") === "de" ? " selected" : ""}>Deutsch</option>
|
||||
<option value="en"${org.output_language === "en" ? " selected" : ""}>English</option>
|
||||
</select></td>`;
|
||||
tbody.innerHTML = licenses.map(l => `
|
||||
<tr>
|
||||
<td><span class="badge badge-${l.license_type}">${l.license_type}</span></td>
|
||||
@@ -446,6 +528,7 @@ async function loadOrgLicenses(orgId) {
|
||||
<td>${formatDate(l.valid_from)}</td>
|
||||
<td>${l.valid_until ? formatDate(l.valid_until) : "Unbegrenzt"}</td>
|
||||
<td><span class="badge badge-${l.status}">${l.status}</span></td>
|
||||
${l.status === "active" ? orgZellen : '<td class="text-muted">–</td><td class="text-muted">–</td>'}
|
||||
<td>
|
||||
${l.status === "active" ? `
|
||||
<button class="btn btn-secondary btn-small" onclick="extendLicense(${l.id})">Verlängern</button>
|
||||
@@ -683,42 +766,27 @@ function setupForms() {
|
||||
}
|
||||
});
|
||||
|
||||
// Org Edit
|
||||
document.getElementById("orgEditForm").addEventListener("submit", async (e) => {
|
||||
// Org umbenennen (kleines Modal, Rest der frueheren Einstellungen ist
|
||||
// inline in Kopfzeile und Lizenz-Tabelle gewandert)
|
||||
const renameForm = document.getElementById("renameOrgForm");
|
||||
if (renameForm) {
|
||||
renameForm.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const errEl = document.getElementById("renameOrgError");
|
||||
if (errEl) errEl.style.display = "none";
|
||||
try {
|
||||
await API.put(`/api/orgs/${currentOrgId}`, {
|
||||
name: document.getElementById("editOrgName").value,
|
||||
is_active: document.getElementById("editOrgActive").value === "true",
|
||||
output_language: document.getElementById("editOrgLanguage").value || "de",
|
||||
name: document.getElementById("renameOrgName").value,
|
||||
});
|
||||
openOrg(currentOrgId);
|
||||
closeModal("modalRenameOrg");
|
||||
currentOrgData = await API.get(`/api/orgs/${currentOrgId}`);
|
||||
renderOrgHeader(currentOrgData);
|
||||
loadOrgs();
|
||||
loadDashboard();
|
||||
} catch (err) {
|
||||
showToast(err.message, "error");
|
||||
if (errEl) { errEl.textContent = err.message; errEl.style.display = "block"; }
|
||||
}
|
||||
});
|
||||
|
||||
// Delete Org
|
||||
document.getElementById("deleteOrgBtn").addEventListener("click", () => {
|
||||
showConfirm(
|
||||
"Organisation löschen",
|
||||
"Soll die Organisation mit allen Nutzern und Lizenzen endgültig gelöscht werden? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
async () => {
|
||||
try {
|
||||
await API.del(`/api/orgs/${currentOrgId}`);
|
||||
document.getElementById("orgListView").style.display = "";
|
||||
document.getElementById("orgDetail").classList.remove("active");
|
||||
currentOrgId = null;
|
||||
loadOrgs();
|
||||
loadDashboard();
|
||||
} catch (err) {
|
||||
showToast(err.message, "error");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Modal helpers ---
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren