From 7d237c866a53f45b4b57d0e675badb5b93ea19fc Mon Sep 17 00:00:00 2001 From: Meleki Date: Tue, 3 Mar 2026 07:40:17 +0100 Subject: [PATCH] =?UTF-8?q?Dashboard:=20Telegram-Verkn=C3=BCpfung=20Button?= =?UTF-8?q?=20+=20Modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- Endpoints/TelegramEndpoint.cs | 8 ++++ wwwroot/css/dashboard.css | 74 +++++++++++++++++++++++++++++++++++ wwwroot/js/api.js | 19 +++++++++ wwwroot/js/pages/dashboard.js | 60 ++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) diff --git a/Endpoints/TelegramEndpoint.cs b/Endpoints/TelegramEndpoint.cs index 9ffa50f..6dd2fea 100644 --- a/Endpoints/TelegramEndpoint.cs +++ b/Endpoints/TelegramEndpoint.cs @@ -22,6 +22,9 @@ public static class TelegramEndpoint // Webhook registrieren (Admin) app.MapPost("/api/telegram/register-webhook", HandleRegisterWebhook); + // Telegram-Status (ob konfiguriert – für Frontend) + app.MapGet("/api/telegram/status", HandleStatus); + // Webhook-Info (Admin) app.MapGet("/api/telegram/webhook-info", HandleWebhookInfo); } @@ -115,6 +118,11 @@ public static class TelegramEndpoint 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) { if (!req.HttpContext.IsAdmin()) diff --git a/wwwroot/css/dashboard.css b/wwwroot/css/dashboard.css index 9afe910..38a60f6 100644 --- a/wwwroot/css/dashboard.css +++ b/wwwroot/css/dashboard.css @@ -301,3 +301,77 @@ background: rgb(from var(--text) r g b / .15); 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); +} diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index 73340dc..e3e734b 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -255,6 +255,25 @@ async function apiChangePassword(currentPassword, newPassword) { 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() { const resp = await fetch('/api/backup', { method: 'POST' }); if (!resp.ok) throw new Error('Backup fehlgeschlagen'); diff --git a/wwwroot/js/pages/dashboard.js b/wwwroot/js/pages/dashboard.js index 1a7d463..487d83b 100644 --- a/wwwroot/js/pages/dashboard.js +++ b/wwwroot/js/pages/dashboard.js @@ -20,6 +20,9 @@ registerPage('dashboard', { // Globaler Click-Handler zum Schließen aller Card-Menüs document.addEventListener('click', this._closeCardMenus = () => closeAllCardMenus()); + // Telegram-Status parallel laden + const telegramStatusPromise = apiTelegramStatus().catch(() => ({ configured: false })); + const agents = await apiGetAgents(); const agentMap = {}; for (const a of agents) agentMap[a.agentId] = a; @@ -52,6 +55,7 @@ registerPage('dashboard', { @@ -82,6 +86,18 @@ registerPage('dashboard', { menuDropdown.hidden = true; 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) => { e.stopPropagation(); 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 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 = ` + + `; + + 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() { document.querySelectorAll('.card-menu-dropdown').forEach(d => d.hidden = true); }