Dashboard: Telegram-Verknüpfung Button + Modal

- Session-Card-Menü: 'Telegram verknüpfen' zwischen Chat öffnen und Bearbeiten
- Button nur sichtbar wenn Telegram Bot konfiguriert (GET /api/telegram/status)
- Klick generiert 6-stelligen Link-Code (POST /api/telegram/link)
- Modal zeigt '/link CODE' mit Copy-Button, 5 Min gültig
- Neuer Backend-Endpoint GET /api/telegram/status (IsConfigured)
- API-Funktionen: apiTelegramStatus(), apiTelegramLink()
- Modal-CSS nach bestehendem .fe-overlay Pattern
This commit is contained in:
Meleki committed 2026-03-03 07:40:17 +01:00
1 parent 24d818f5d2
commit 7d237c866a
4 files changed
+161

No files matched your search

+8
View File
@@ -22,6 +22,9 @@ public static class TelegramEndpoint
// Webhook registrieren (Admin) // Webhook registrieren (Admin)
app.MapPost("/api/telegram/register-webhook", HandleRegisterWebhook); app.MapPost("/api/telegram/register-webhook", HandleRegisterWebhook);
// Telegram-Status (ob konfiguriert – für Frontend)
app.MapGet("/api/telegram/status", HandleStatus);
// Webhook-Info (Admin) // Webhook-Info (Admin)
app.MapGet("/api/telegram/webhook-info", HandleWebhookInfo); app.MapGet("/api/telegram/webhook-info", HandleWebhookInfo);
} }
@@ -115,6 +118,11 @@ public static class TelegramEndpoint
await res.WriteAsJsonAsync(new { result }); await res.WriteAsJsonAsync(new { result });
} }
private static async Task HandleStatus(HttpResponse res, TelegramService telegram)
{
await res.WriteAsJsonAsync(new { configured = telegram.IsConfigured });
}
private static async Task HandleWebhookInfo(HttpRequest req, HttpResponse res, TelegramService telegram) private static async Task HandleWebhookInfo(HttpRequest req, HttpResponse res, TelegramService telegram)
{ {
if (!req.HttpContext.IsAdmin()) if (!req.HttpContext.IsAdmin())
+74
View File
@@ -301,3 +301,77 @@
background: rgb(from var(--text) r g b / .15); background: rgb(from var(--text) r g b / .15);
color: var(--text); color: var(--text);
} }
/* Telegram Link Modal */
.modal-overlay {
position: fixed;
inset: 0;
background: rgb(from var(--f1-bg) r g b / var(--semi-transparent));
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.telegram-link-modal {
background: var(--f2-bg);
border: var(--border-width) solid var(--f2-border);
border-radius: var(--border-radius);
width: 90vw;
max-width: 28em;
display: flex;
flex-direction: column;
overflow: hidden;
}
.telegram-link-modal .modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--box-padding);
border-bottom: var(--border-width) solid var(--f1-border);
background: var(--f1-bg);
}
.telegram-link-modal .modal-header h3 {
margin: 0;
display: flex;
align-items: center;
gap: var(--badge-spacing);
}
.telegram-link-modal .modal-body {
padding: var(--box-padding);
display: flex;
flex-direction: column;
gap: var(--box-spacing);
}
.telegram-code-display {
display: flex;
align-items: center;
justify-content: center;
gap: var(--box-spacing);
padding: var(--box-padding);
background: var(--f1-bg);
border: var(--border-width) solid var(--f1-border);
border-radius: var(--border-radius);
}
.telegram-code-display code {
font-size: calc(var(--font-size) * 1.5);
font-weight: bold;
letter-spacing: .1em;
color: var(--primary);
user-select: all;
}
.telegram-code-hint {
opacity: var(--semi-transparent);
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: var(--badge-spacing);
}
+19
View File
@@ -255,6 +255,25 @@ async function apiChangePassword(currentPassword, newPassword) {
return resp.json(); return resp.json();
} }
async function apiTelegramStatus() {
const resp = await fetch('/api/telegram/status');
if (!resp.ok) return { configured: false };
return resp.json();
}
async function apiTelegramLink(sessionId) {
const resp = await fetch('/api/telegram/link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId })
});
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
throw new Error(err.error || 'Telegram-Link fehlgeschlagen');
}
return resp.json();
}
async function apiBackupDb() { async function apiBackupDb() {
const resp = await fetch('/api/backup', { method: 'POST' }); const resp = await fetch('/api/backup', { method: 'POST' });
if (!resp.ok) throw new Error('Backup fehlgeschlagen'); if (!resp.ok) throw new Error('Backup fehlgeschlagen');
+60
View File
@@ -20,6 +20,9 @@ registerPage('dashboard', {
// Globaler Click-Handler zum Schließen aller Card-Menüs // Globaler Click-Handler zum Schließen aller Card-Menüs
document.addEventListener('click', this._closeCardMenus = () => closeAllCardMenus()); document.addEventListener('click', this._closeCardMenus = () => closeAllCardMenus());
// Telegram-Status parallel laden
const telegramStatusPromise = apiTelegramStatus().catch(() => ({ configured: false }));
const agents = await apiGetAgents(); const agents = await apiGetAgents();
const agentMap = {}; const agentMap = {};
for (const a of agents) agentMap[a.agentId] = a; for (const a of agents) agentMap[a.agentId] = a;
@@ -52,6 +55,7 @@ registerPage('dashboard', {
<button class="card-menu-trigger icon-btn"><i class="fa fa-ellipsis-v"></i></button> <button class="card-menu-trigger icon-btn"><i class="fa fa-ellipsis-v"></i></button>
<div class="card-menu-dropdown" hidden> <div class="card-menu-dropdown" hidden>
<button class="card-menu-item" data-action="chat"><i class="fa fa-comment"></i> Chat öffnen</button> <button class="card-menu-item" data-action="chat"><i class="fa fa-comment"></i> Chat öffnen</button>
<button class="card-menu-item" data-action="telegram" hidden><i class="fa fa-paper-plane"></i> Telegram verknüpfen</button>
<button class="card-menu-item" data-action="edit"><i class="fa fa-pencil"></i> Bearbeiten</button> <button class="card-menu-item" data-action="edit"><i class="fa fa-pencil"></i> Bearbeiten</button>
</div> </div>
</div> </div>
@@ -82,6 +86,18 @@ registerPage('dashboard', {
menuDropdown.hidden = true; menuDropdown.hidden = true;
navigateTo('chat', { id: s.sessionId }); navigateTo('chat', { id: s.sessionId });
}); });
const telegramBtn = menuDropdown.querySelector('[data-action="telegram"]');
telegramBtn.addEventListener('click', async (e) => {
e.stopPropagation();
menuDropdown.hidden = true;
try {
const result = await apiTelegramLink(s.sessionId);
showTelegramLinkCode(result.code, s.name || 'Unbenannt');
} catch (err) {
showToast(err.message, 'error');
}
});
menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => { menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
menuDropdown.hidden = true; menuDropdown.hidden = true;
@@ -128,6 +144,13 @@ registerPage('dashboard', {
} }
} }
// Telegram-Buttons einblenden wenn konfiguriert
telegramStatusPromise.then(status => {
if (status.configured) {
grid.querySelectorAll('[data-action="telegram"]').forEach(btn => btn.hidden = false);
}
});
// Neue Session Button // Neue Session Button
document.getElementById('newSessionBtn').addEventListener('click', () => navigateTo('session')); document.getElementById('newSessionBtn').addEventListener('click', () => navigateTo('session'));
@@ -235,6 +258,43 @@ async function loadClaudeStatus() {
} }
} }
function showTelegramLinkCode(code, sessionName) {
// Bestehendes Modal entfernen
const existing = document.getElementById('telegramLinkModal');
if (existing) existing.remove();
const modal = document.createElement('div');
modal.id = 'telegramLinkModal';
modal.className = 'modal-overlay';
modal.innerHTML = `
<div class="modal telegram-link-modal">
<div class="modal-header">
<h3><i class="fa fa-paper-plane"></i> Telegram verknüpfen</h3>
<button class="icon-btn modal-close"><i class="fa fa-times"></i></button>
</div>
<div class="modal-body">
<p>Sende diesen Code an deinen Telegram-Bot um die Session <strong>${esc(sessionName)}</strong> zu verknüpfen:</p>
<div class="telegram-code-display">
<code>/link ${esc(code)}</code>
<button class="icon-btn" title="Kopieren" id="copyTelegramCode"><i class="fa fa-copy"></i></button>
</div>
<p class="telegram-code-hint"><i class="fa fa-clock-o"></i> Der Code ist 5 Minuten gültig.</p>
</div>
</div>
`;
document.body.appendChild(modal);
modal.querySelector('.modal-close').addEventListener('click', () => modal.remove());
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
document.getElementById('copyTelegramCode').addEventListener('click', () => {
navigator.clipboard.writeText('/link ' + code).then(() => {
showToast('In Zwischenablage kopiert', 'success');
});
});
}
function closeAllCardMenus() { function closeAllCardMenus() {
document.querySelectorAll('.card-menu-dropdown').forEach(d => d.hidden = true); document.querySelectorAll('.card-menu-dropdown').forEach(d => d.hidden = true);
} }