feat(bugtracker, tokens): Add project search, push_id, target_agent, tags, edit modal, extended severities (idea, wishlist), token actions (copy/toggle/delete), and agent integration guide
This commit is contained in:
+264
-50
@@ -437,6 +437,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === 'delete_core_token') {
|
||||
$tokenId = trim($_POST['token_id'] ?? '');
|
||||
if ($tokenId) {
|
||||
$coreTokenMgr = new CoreTokenManager($pdo);
|
||||
$coreTokenMgr->deleteToken($tokenId);
|
||||
$msg = "Token '{$tokenId}' und abgeleitete Sub-Tokens wurden dauerhaft gelöscht.";
|
||||
}
|
||||
}
|
||||
|
||||
// Bugtracker & Feature-Tracker Actions
|
||||
if ($action === 'bt_add_comment') {
|
||||
$itemId = (int)($_POST['item_id'] ?? 0);
|
||||
@@ -474,6 +483,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$build = trim($_POST['build_version'] ?? 'v1.0.0');
|
||||
$env = $_POST['environment'] ?? 'production';
|
||||
$sev = $_POST['severity'] ?? 'medium';
|
||||
$pushId = trim($_POST['push_id'] ?? '');
|
||||
$agent = trim($_POST['target_agent'] ?? '');
|
||||
$tags = trim($_POST['tags'] ?? '');
|
||||
$createdBy = trim($_POST['created_by'] ?? $_SESSION['dc_username'] ?? 'admin');
|
||||
|
||||
if ($title) {
|
||||
@@ -488,6 +500,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
'build_version' => $build,
|
||||
'environment' => $env,
|
||||
'severity' => $sev,
|
||||
'push_id' => $pushId,
|
||||
'target_agent' => $agent,
|
||||
'tags' => $tags,
|
||||
'created_by' => $createdBy,
|
||||
]);
|
||||
$msg = $res['is_new']
|
||||
@@ -508,6 +523,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$msg = "Status für Item #{$itemId} geändert auf {$status}.";
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === 'bt_update_item') {
|
||||
$itemId = (int)($_POST['item_id'] ?? 0);
|
||||
$status = $_POST['status'] ?? 'open';
|
||||
$sev = $_POST['severity'] ?? 'medium';
|
||||
$pushId = trim($_POST['push_id'] ?? '');
|
||||
$agent = trim($_POST['target_agent'] ?? '');
|
||||
$tags = trim($_POST['tags'] ?? '');
|
||||
$notes = trim($_POST['resolution_notes'] ?? '');
|
||||
$build = trim($_POST['resolved_in_build'] ?? '');
|
||||
$author = trim($_POST['author'] ?? $_SESSION['dc_username'] ?? 'admin');
|
||||
|
||||
if ($itemId > 0) {
|
||||
$bugRepo = new BugRepo($pdo);
|
||||
$bugRepo->updateItemDetails($itemId, [
|
||||
'status' => $status,
|
||||
'severity' => $sev,
|
||||
'push_id' => $pushId,
|
||||
'target_agent' => $agent,
|
||||
'tags' => $tags,
|
||||
'resolution_notes' => $notes,
|
||||
'resolved_in_build' => $build,
|
||||
], $author);
|
||||
$msg = "Item #{$itemId} wurde erfolgreich aktualisiert.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch All Data
|
||||
@@ -1838,7 +1879,7 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<div id="tab-bugtracker" class="tab-content">
|
||||
|
||||
<div id="sub-bugtracker-items" class="subtab-content active">
|
||||
<div class="stats-grid">
|
||||
<div class="stats-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">Dev Bugs (Entwicklung)</div>
|
||||
<div class="stat-value" style="color:#5b9dff;"><?= $bugtrackerStats['open_bugs_dev'] ?></div>
|
||||
@@ -1851,6 +1892,10 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<div class="stat-header">Offene Feature Requests</div>
|
||||
<div class="stat-value" style="color:var(--warning);"><?= $bugtrackerStats['open_features'] ?></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">💡 Ideen & Gedanken</div>
|
||||
<div class="stat-value" style="color:#e082ff;"><?= $bugtrackerStats['ideas_count'] ?></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">Gelöst / Umgesetzt</div>
|
||||
<div class="stat-value" style="color:var(--success);"><?= $bugtrackerStats['resolved_total'] ?></div>
|
||||
@@ -1858,26 +1903,46 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<h2 class="card-title">🐛 Bugs & Feature Requests</h2>
|
||||
<div style="display:flex; gap:0.5rem;">
|
||||
<div class="card-header" style="display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:0.75rem;">
|
||||
<h2 class="card-title" style="margin:0;">🐛 Bugs, Features & Ideen</h2>
|
||||
<div style="display:flex; flex-wrap:wrap; gap:0.4rem; align-items:center;">
|
||||
<select id="btFilterProject" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="filterBugtrackerTable()">
|
||||
<option value="all">🌐 Alle Projekte</option>
|
||||
<?php foreach ($projects as $p): ?>
|
||||
<option value="<?= htmlspecialchars($p['slug']) ?>"><?= htmlspecialchars($p['name']) ?> (<?= htmlspecialchars($p['slug']) ?>)</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select id="btFilterEnv" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="filterBugtrackerTable()">
|
||||
<option value="all">🌐 Alle Umgebungen</option>
|
||||
<option value="all">🌍 Alle Umgebungen</option>
|
||||
<option value="production">🔴 Produktion (Prod)</option>
|
||||
<option value="development">🔵 Entwicklung (Dev)</option>
|
||||
<option value="staging">🟡 Staging</option>
|
||||
<option value="testing">🧪 Testing</option>
|
||||
</select>
|
||||
<select id="btFilterType" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="filterBugtrackerTable()">
|
||||
<option value="all">📂 Alle Typen</option>
|
||||
<option value="bug">🐛 Nur Bugs</option>
|
||||
<option value="feature_request">💡 Nur Feature Requests</option>
|
||||
</select>
|
||||
<select id="btFilterSeverity" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="filterBugtrackerTable()">
|
||||
<option value="all">🔥 Alle Schweregrade</option>
|
||||
<option value="idea">💡 Nur Ideen & Gedanken</option>
|
||||
<option value="wishlist">⭐ Nur Wunschliste</option>
|
||||
<option value="low">Niedrig</option>
|
||||
<option value="medium">Mittel</option>
|
||||
<option value="high">Hoch</option>
|
||||
<option value="critical">🔥 Kritisch</option>
|
||||
</select>
|
||||
<select id="btFilterStatus" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="filterBugtrackerTable()">
|
||||
<option value="all">📌 Alle Status</option>
|
||||
<option value="open">Offen</option>
|
||||
<option value="planned">Geplant</option>
|
||||
<option value="in_progress">In Bearbeitung</option>
|
||||
<option value="resolved">Gelöst / Umgesetzt</option>
|
||||
<option value="closed">Geschlossen</option>
|
||||
<option value="rejected">Abgelehnt</option>
|
||||
</select>
|
||||
<input type="text" id="btFilterSearch" class="form-input" style="width:160px; padding:0.3rem 0.6rem; font-size:0.8rem;" placeholder="🔍 Suche..." onkeyup="filterBugtrackerTable()">
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
@@ -1908,6 +1973,7 @@ $baseUrl = $protocol . '://' . $host;
|
||||
|
||||
$statusBadge = match($item['status']) {
|
||||
'open' => '<span class="badge badge-down">OFFEN</span>',
|
||||
'planned' => '<span class="badge" style="background:rgba(255,165,0,0.2); color:orange; border:1px solid orange;">GEPLANT</span>',
|
||||
'in_progress' => '<span class="badge badge-warning">IN BEARBEITUNG</span>',
|
||||
'resolved' => '<span class="badge badge-up">GELÖST / UMGESETZT</span>',
|
||||
'rejected' => '<span class="badge badge-stopped">ABGELEHNT</span>',
|
||||
@@ -1915,22 +1981,38 @@ $baseUrl = $protocol . '://' . $host;
|
||||
};
|
||||
|
||||
$sevBadge = match($item['severity']) {
|
||||
'idea' => '<span class="badge" style="background:rgba(215,100,255,0.25); color:#e082ff; border:1px solid #e082ff;">💡 IDEE</span>',
|
||||
'wishlist' => '<span class="badge" style="background:rgba(0,210,255,0.25); color:#5ce1e6; border:1px solid #5ce1e6;">⭐ WUNSCHLISTE</span>',
|
||||
'critical' => '<span class="badge badge-down" style="font-weight:bold;">🔥 KRITISCH</span>',
|
||||
'high' => '<span class="badge badge-warning">HOCH</span>',
|
||||
'medium' => '<span class="badge badge-stopped">MITTEL</span>',
|
||||
default => '<span class="badge badge-stopped">NIEDRIG</span>'
|
||||
};
|
||||
|
||||
$jsonItem = htmlspecialchars(json_encode($item), ENT_QUOTES, 'UTF-8');
|
||||
?>
|
||||
<tr class="bt-row"
|
||||
data-project="<?= htmlspecialchars($item['project_slug']) ?>"
|
||||
data-env="<?= htmlspecialchars($item['environment']) ?>"
|
||||
data-type="<?= htmlspecialchars($item['type']) ?>"
|
||||
data-severity="<?= htmlspecialchars($item['severity']) ?>"
|
||||
data-status="<?= htmlspecialchars($item['status']) ?>">
|
||||
<td>#<?= $item['id'] ?><br><?= $typeBadge ?></td>
|
||||
<td><?= $envBadge ?></td>
|
||||
<td>
|
||||
<strong style="color:#fff;"><?= htmlspecialchars($item['title']) ?></strong>
|
||||
<div style="font-size:0.75rem; color:var(--text-muted);">
|
||||
Projekt: <code><?= htmlspecialchars($item['project_slug']) ?></code> | Von: <?= htmlspecialchars($item['created_by']) ?>
|
||||
<div style="font-size:0.75rem; color:var(--text-muted); margin-top:0.2rem; display:flex; flex-wrap:wrap; gap:0.4rem; align-items:center;">
|
||||
<span>Projekt: <code><?= htmlspecialchars($item['project_slug']) ?></code></span>
|
||||
<span>Von: <?= htmlspecialchars($item['created_by']) ?></span>
|
||||
<?php if (!empty($item['push_id'])): ?>
|
||||
<span class="badge" style="background:rgba(255,255,255,0.06); font-family:monospace; font-size:0.7rem;">📲 Push: <?= htmlspecialchars($item['push_id']) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($item['target_agent'])): ?>
|
||||
<span class="badge" style="background:rgba(0,200,100,0.15); color:#66bb6a; font-size:0.7rem;">🤖 <?= htmlspecialchars($item['target_agent']) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($item['tags'])): ?>
|
||||
<span class="badge" style="background:rgba(100,180,255,0.15); color:#90caf9; font-size:0.7rem;">🏷️ <?= htmlspecialchars($item['tags']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td><code><?= htmlspecialchars($item['build_version'] ?? 'v1.0.0') ?></code></td>
|
||||
@@ -1942,10 +2024,13 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</td>
|
||||
<td><?= $statusBadge ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="openBugtrackerModal(<?= $item['id'] ?>)">🔍 Details & Timeline</button>
|
||||
<?php if ($item['status'] !== 'resolved'): ?>
|
||||
<button type="button" class="btn btn-sm" onclick="openResolveModal(<?= $item['id'] ?>, '<?= htmlspecialchars(addslashes($item['title'])) ?>')">✔ Gelöst</button>
|
||||
<?php endif; ?>
|
||||
<div style="display:flex; gap:0.3rem;">
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="openEditBugtrackerModal(<?= $jsonItem ?>)">✏️ Edit</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="openBugtrackerModal(<?= $item['id'] ?>)">🔍 Details</button>
|
||||
<?php if ($item['status'] !== 'resolved'): ?>
|
||||
<button type="button" class="btn btn-sm" onclick="openResolveModal(<?= $item['id'] ?>, '<?= htmlspecialchars(addslashes($item['title'])) ?>')">✔ Gelöst</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@@ -1957,7 +2042,7 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<!-- Subtab: Item manuell anlegen -->
|
||||
<div id="sub-bugtracker-new" class="subtab-content">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2 class="card-title">➕ Bug oder Feature Request Manuell Erfassen</h2></div>
|
||||
<div class="card-header"><h2 class="card-title">➕ Bug, Feature Request oder Idee Erfassen</h2></div>
|
||||
<form method="POST" action="index.php#tab-bugtracker">
|
||||
<input type="hidden" name="action" value="bt_create_item">
|
||||
<div class="form-grid">
|
||||
@@ -1973,7 +2058,7 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<label class="form-label">Typ</label>
|
||||
<select name="type" class="form-input" required>
|
||||
<option value="bug" selected>🐛 Bug / Fehlerbericht</option>
|
||||
<option value="feature_request">💡 Feature Request / Vorschlag</option>
|
||||
<option value="feature_request">💡 Feature Request / Vorschlag / Idee</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -1988,6 +2073,8 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<div class="form-group">
|
||||
<label class="form-label">Schweregrad / Priorität</label>
|
||||
<select name="severity" class="form-input" required>
|
||||
<option value="idea">💡 Idee / Gedanke für Später</option>
|
||||
<option value="wishlist">⭐ Wunschliste / Backlog</option>
|
||||
<option value="low">Niedrig</option>
|
||||
<option value="medium" selected>Mittel</option>
|
||||
<option value="high">Hoch</option>
|
||||
@@ -1995,9 +2082,23 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid" style="margin-top:1rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Push-ID (Optional, z. B. Workflow / Notification Token)</label>
|
||||
<input type="text" name="push_id" class="form-input" placeholder="z. B. push_notify_88192">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Ziel-Agent / Zuweisung (Optional)</label>
|
||||
<input type="text" name="target_agent" class="form-input" placeholder="z. B. agent:code-fixer-01">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Tags / Kategorien (Kommagetrennt)</label>
|
||||
<input type="text" name="tags" class="form-input" placeholder="z. B. ui, security, database">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:1rem;">
|
||||
<label class="form-label">Titel / Zusammenfassung</label>
|
||||
<input type="text" name="title" class="form-input" required placeholder="z. B. NullReferenceException bei Order-Submit">
|
||||
<input type="text" name="title" class="form-input" required placeholder="z. B. NullReferenceException bei Order-Submit oder Idee für TOTP 2FA">
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:1rem;">
|
||||
<label class="form-label">Build / Version</label>
|
||||
@@ -2005,14 +2106,14 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:1rem;">
|
||||
<label class="form-label">Detaillierte Beschreibung</label>
|
||||
<textarea name="description" class="form-input" rows="3" placeholder="Was ist passiert? Unter welchen Bedingungen?"></textarea>
|
||||
<textarea name="description" class="form-input" rows="3" placeholder="Was ist passiert? Unter welchen Bedingungen? Oder was soll umgesetzt werden?"></textarea>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:1rem;">
|
||||
<label class="form-label">Fehlermeldung / Exception Message</label>
|
||||
<label class="form-label">Fehlermeldung / Exception Message (Nur bei Bugs)</label>
|
||||
<textarea name="error_message" class="form-input" rows="2" placeholder="Exakte Fehlermeldung aus dem Log..."></textarea>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:1rem;">
|
||||
<label class="form-label">Stacktrace / Log Ausschnitt</label>
|
||||
<label class="form-label">Stacktrace / Log Ausschnitt (Nur bei Bugs)</label>
|
||||
<textarea name="stack_trace" class="form-input" rows="4" style="font-family:monospace;" placeholder="at MyApp.Core.Service.DoWork()..."></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn" style="margin-top:1rem;">Item Speichern & Anlegen</button>
|
||||
@@ -2068,22 +2169,29 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<span class="badge badge-up"><?= (int)($mTok['sub_token_count'] ?? 0) ?> Sub-Tokens</span>
|
||||
</td>
|
||||
<td>
|
||||
<code id="tok-core-text-<?= $mTok['token_id'] ?>" data-full="<?= htmlspecialchars($rawVal) ?>" data-masked="<?= htmlspecialchars($masked) ?>">
|
||||
<code id="tok-core-<?= $mTok['token_id'] ?>" data-full="<?= htmlspecialchars($rawVal) ?>" data-masked="<?= htmlspecialchars($masked) ?>">
|
||||
<?= htmlspecialchars($masked) ?>
|
||||
</code>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="toggleTokenMask('core-text-<?= $mTok['token_id'] ?>')">👁️</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="copyTokenValue('core-text-<?= $mTok['token_id'] ?>')">📋</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="toggleTokenMask('tok-core-<?= $mTok['token_id'] ?>')">👁️</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="copyTokenValue('tok-core-<?= $mTok['token_id'] ?>')">📋</button>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!$mTok['revoked']): ?>
|
||||
<form method="POST" action="index.php#tab-tokens" style="display:inline" onsubmit="return confirm('Master-Token widerrufen? Alle zugehörigen Sub-Tokens werden sofort kaskadierend mit gesperrt!');">
|
||||
<input type="hidden" name="action" value="revoke_core_token">
|
||||
<div style="display:flex; gap:0.3rem;">
|
||||
<?php if (!$mTok['revoked']): ?>
|
||||
<form method="POST" action="index.php#tab-tokens" style="display:inline" onsubmit="return confirm('Master-Token widerrufen? Alle zugehörigen Sub-Tokens werden sofort kaskadierend mit gesperrt!');">
|
||||
<input type="hidden" name="action" value="revoke_core_token">
|
||||
<input type="hidden" name="token_id" value="<?= $mTok['token_id'] ?>">
|
||||
<button type="submit" class="btn btn-sm btn-warning">Widerrufen</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-down">WIDERUFEN</span>
|
||||
<?php endif; ?>
|
||||
<form method="POST" action="index.php#tab-tokens" style="display:inline" onsubmit="return confirm('Master-Token und alle Sub-Tokens DAUERHAFT LÖSCHEN?');">
|
||||
<input type="hidden" name="action" value="delete_core_token">
|
||||
<input type="hidden" name="token_id" value="<?= $mTok['token_id'] ?>">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Master & Subs Widerrufen</button>
|
||||
<button type="submit" class="btn btn-sm btn-danger">🗑️ Löschen</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-down">WIDERUFEN</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@@ -2103,8 +2211,10 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<th>Bezeichnung / Client</th>
|
||||
<th>Umgebung</th>
|
||||
<th>Scopes</th>
|
||||
<th>Token Key</th>
|
||||
<th>Zuletzt Genutzt</th>
|
||||
<th>Status</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -2112,6 +2222,8 @@ $baseUrl = $protocol . '://' . $host;
|
||||
$subTokens = array_filter($coreAllTokens, fn($t) => $t['type'] === 'sub');
|
||||
foreach ($subTokens as $sTok):
|
||||
$scopesArr = json_decode($sTok['scopes'], true) ?: [];
|
||||
$sRaw = !empty($sTok['raw_token']) ? $sTok['raw_token'] : $sTok['token_id'];
|
||||
$sMasked = (strlen($sRaw) > 12) ? substr($sRaw, 0, 12) . '••••••••••••••••' : $sRaw;
|
||||
?>
|
||||
<tr>
|
||||
<td><code><?= htmlspecialchars($sTok['token_id']) ?></code></td>
|
||||
@@ -2125,8 +2237,31 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<span class="badge" style="background:rgba(255,255,255,0.06); font-size:0.75rem;"><?= htmlspecialchars($sc) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</td>
|
||||
<td>
|
||||
<code id="tok-sub-<?= $sTok['token_id'] ?>" data-full="<?= htmlspecialchars($sRaw) ?>" data-masked="<?= htmlspecialchars($sMasked) ?>">
|
||||
<?= htmlspecialchars($sMasked) ?>
|
||||
</code>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="toggleTokenMask('tok-sub-<?= $sTok['token_id'] ?>')">👁️</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="copyTokenValue('tok-sub-<?= $sTok['token_id'] ?>')">📋</button>
|
||||
</td>
|
||||
<td><?= $sTok['last_used_at'] ? htmlspecialchars($sTok['last_used_at']) : 'Noch nie' ?></td>
|
||||
<td><span class="badge badge-<?= $sTok['revoked'] ? 'down' : 'up' ?>"><?= $sTok['revoked'] ? 'WIDERUFEN' : 'AKTIV' ?></span></td>
|
||||
<td>
|
||||
<div style="display:flex; gap:0.3rem;">
|
||||
<?php if (!$sTok['revoked']): ?>
|
||||
<form method="POST" action="index.php#tab-tokens" style="display:inline" onsubmit="return confirm('Sub-Token widerrufen?');">
|
||||
<input type="hidden" name="action" value="revoke_core_token">
|
||||
<input type="hidden" name="token_id" value="<?= $sTok['token_id'] ?>">
|
||||
<button type="submit" class="btn btn-sm btn-warning">Widerrufen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<form method="POST" action="index.php#tab-tokens" style="display:inline" onsubmit="return confirm('Sub-Token DAUERHAFT LÖSCHEN?');">
|
||||
<input type="hidden" name="action" value="delete_core_token">
|
||||
<input type="hidden" name="token_id" value="<?= $sTok['token_id'] ?>">
|
||||
<button type="submit" class="btn btn-sm btn-danger">🗑️ Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
@@ -2393,16 +2528,23 @@ $baseUrl = $protocol . '://' . $host;
|
||||
|
||||
// Bugtracker Table Filter JS
|
||||
function filterBugtrackerTable() {
|
||||
const project = document.getElementById('btFilterProject').value;
|
||||
const env = document.getElementById('btFilterEnv').value;
|
||||
const type = document.getElementById('btFilterType').value;
|
||||
const severity = document.getElementById('btFilterSeverity').value;
|
||||
const status = document.getElementById('btFilterStatus').value;
|
||||
const query = (document.getElementById('btFilterSearch').value || '').toLowerCase().trim();
|
||||
|
||||
document.querySelectorAll('#btTableBody .bt-row').forEach(row => {
|
||||
const matchProject = (project === 'all' || row.getAttribute('data-project') === project);
|
||||
const matchEnv = (env === 'all' || row.getAttribute('data-env') === env);
|
||||
const matchType = (type === 'all' || row.getAttribute('data-type') === type);
|
||||
const matchSeverity = (severity === 'all' || row.getAttribute('data-severity') === severity);
|
||||
const matchStatus = (status === 'all' || row.getAttribute('data-status') === status);
|
||||
const textContent = row.innerText.toLowerCase();
|
||||
const matchQuery = (!query || textContent.includes(query));
|
||||
|
||||
if (matchEnv && matchType && matchStatus) {
|
||||
if (matchProject && matchEnv && matchType && matchSeverity && matchStatus && matchQuery) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
@@ -2410,6 +2552,24 @@ $baseUrl = $protocol . '://' . $host;
|
||||
});
|
||||
}
|
||||
|
||||
// Open Bugtracker Edit Modal
|
||||
function openEditBugtrackerModal(item) {
|
||||
document.getElementById('btEditItemId').value = item.id;
|
||||
document.getElementById('btEditTitleDisplay').innerText = '#' + item.id + ': ' + item.title;
|
||||
document.getElementById('btEditStatus').value = item.status;
|
||||
document.getElementById('btEditSeverity').value = item.severity;
|
||||
document.getElementById('btEditPushId').value = item.push_id || '';
|
||||
document.getElementById('btEditTargetAgent').value = item.target_agent || '';
|
||||
document.getElementById('btEditTags').value = item.tags || '';
|
||||
document.getElementById('btEditResolvedInBuild').value = item.resolved_in_build || '';
|
||||
document.getElementById('btEditResolutionNotes').value = item.resolution_notes || '';
|
||||
document.getElementById('btEditModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeEditBugtrackerModal() {
|
||||
document.getElementById('btEditModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// Open Bugtracker Item Details & Timeline Modal
|
||||
function openBugtrackerModal(itemId) {
|
||||
const modal = document.getElementById('btDetailModal');
|
||||
@@ -2640,9 +2800,10 @@ $baseUrl = $protocol . '://' . $host;
|
||||
document.getElementById('editExpiresAt').value = opt.getAttribute('data-exp') || '';
|
||||
}
|
||||
|
||||
// Token Masking Toggle
|
||||
// Token Masking Toggle & Copy
|
||||
function toggleTokenMask(tokId) {
|
||||
const el = document.getElementById('tok-text-' + tokId);
|
||||
const el = document.getElementById(tokId) || document.getElementById('tok-text-' + tokId) || document.getElementById('tok-core-' + tokId) || document.getElementById('tok-sub-' + tokId);
|
||||
if (!el) return;
|
||||
if (el.innerText.includes('••••')) {
|
||||
el.innerText = el.getAttribute('data-full');
|
||||
} else {
|
||||
@@ -2651,10 +2812,18 @@ $baseUrl = $protocol . '://' . $host;
|
||||
}
|
||||
|
||||
function copyTokenValue(tokId) {
|
||||
const el = document.getElementById('tok-text-' + tokId);
|
||||
const el = document.getElementById(tokId) || document.getElementById('tok-text-' + tokId) || document.getElementById('tok-core-' + tokId) || document.getElementById('tok-sub-' + tokId);
|
||||
if (!el) return;
|
||||
const fullVal = el.getAttribute('data-full');
|
||||
navigator.clipboard.writeText(fullVal);
|
||||
alert('Token in Zwischenablage kopiert!');
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(fullVal).then(() => {
|
||||
alert('Token in Zwischenablage kopiert!');
|
||||
}).catch(() => {
|
||||
prompt('Token kopieren:', fullVal);
|
||||
});
|
||||
} else {
|
||||
prompt('Token kopieren:', fullVal);
|
||||
}
|
||||
}
|
||||
|
||||
function generateOfflinePayload() {
|
||||
@@ -2709,28 +2878,73 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bugtracker Resolve Modal -->
|
||||
<div id="btResolveModal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.75); backdrop-filter:blur(8px); z-index:9999; align-items:center; justify-content:center; padding:1rem;">
|
||||
<div class="card" style="width:100%; max-width:500px; background:#161c28; border:1px solid var(--border-glass);">
|
||||
<!-- Bugtracker Edit Modal -->
|
||||
<div id="btEditModal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.75); backdrop-filter:blur(8px); z-index:9999; align-items:center; justify-content:center; padding:1rem;">
|
||||
<div class="card" style="width:100%; max-width:650px; background:#161c28; border:1px solid var(--border-glass);">
|
||||
<div class="card-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<h3 class="card-title" style="margin:0; color:#fff;">✔ Item als Gelöst / Umgesetzt Markieren</h3>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="closeResolveModal()">✕</button>
|
||||
<h3 class="card-title" style="margin:0; color:#fff;">✏️ Item Bearbeiten</h3>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="closeEditBugtrackerModal()">✕</button>
|
||||
</div>
|
||||
<p style="font-size:0.875rem; color:var(--text-muted); margin-bottom:1rem;" id="resolveItemTitle"></p>
|
||||
<p style="font-size:0.875rem; color:var(--text-muted); margin-bottom:1rem;" id="btEditTitleDisplay"></p>
|
||||
<form method="POST" action="index.php#tab-bugtracker">
|
||||
<input type="hidden" name="action" value="bt_resolve">
|
||||
<input type="hidden" name="item_id" id="resolveItemId" value="0">
|
||||
<div class="form-group" style="margin-bottom:1rem;">
|
||||
<label class="form-label">Lösungs-Build / Version (z. B. v1.4.3)</label>
|
||||
<input type="text" name="resolved_in_build" class="form-input" required value="v1.4.3" placeholder="v1.4.3">
|
||||
<input type="hidden" name="action" value="bt_update_item">
|
||||
<input type="hidden" name="item_id" id="btEditItemId" value="0">
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Status Bearbeiten</label>
|
||||
<select name="status" id="btEditStatus" class="form-input" required>
|
||||
<option value="open">Offen</option>
|
||||
<option value="planned">Geplant</option>
|
||||
<option value="in_progress">In Bearbeitung</option>
|
||||
<option value="resolved">Gelöst / Umgesetzt</option>
|
||||
<option value="closed">Geschlossen</option>
|
||||
<option value="rejected">Abgelehnt</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Schweregrad Bearbeiten</label>
|
||||
<select name="severity" id="btEditSeverity" class="form-input" required>
|
||||
<option value="idea">💡 Idee / Gedanke für Später</option>
|
||||
<option value="wishlist">⭐ Wunschliste / Backlog</option>
|
||||
<option value="low">Niedrig</option>
|
||||
<option value="medium">Mittel</option>
|
||||
<option value="high">Hoch</option>
|
||||
<option value="critical">🔥 Kritisch</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:1.25rem;">
|
||||
<label class="form-label">Lösungs-Notizen / Dokumentation</label>
|
||||
<textarea name="resolution_notes" class="form-input" rows="3" placeholder="Kurze Beschreibung, wie das Problem behoben oder das Feature umgesetzt wurde..."></textarea>
|
||||
|
||||
<div class="form-grid" style="margin-top:1rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Push-ID (Workflow / Push Notification Token)</label>
|
||||
<input type="text" name="push_id" id="btEditPushId" class="form-input" placeholder="z. B. push_notify_99182">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Ziel-Agent / Zuweisung</label>
|
||||
<input type="text" name="target_agent" id="btEditTargetAgent" class="form-input" placeholder="z. B. agent:dev-monitor-01">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:flex-end; gap:0.5rem;">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeResolveModal()">Abbrechen</button>
|
||||
<button type="submit" class="btn">Als Gelöst Speichern</button>
|
||||
|
||||
<div class="form-group" style="margin-top:1rem;">
|
||||
<label class="form-label">Tags (Kommagetrennt)</label>
|
||||
<input type="text" name="tags" id="btEditTags" class="form-input" placeholder="z. B. ui, security, database">
|
||||
</div>
|
||||
|
||||
<div class="form-grid" style="margin-top:1rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Lösungs-Build (Falls gelöst)</label>
|
||||
<input type="text" name="resolved_in_build" id="btEditResolvedInBuild" class="form-input" placeholder="v1.4.3">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Lösungs-Notizen / Dokumentation</label>
|
||||
<input type="text" name="resolution_notes" id="btEditResolutionNotes" class="form-input" placeholder="Note zur Behebung...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:flex-end; gap:0.5rem; margin-top:1.25rem;">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeEditBugtrackerModal()">Abbrechen</button>
|
||||
<button type="submit" class="btn">Änderungen Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user