Telegram: Webhook-Verwaltung in Einstellungen-Seite
- Backend: DELETE /api/telegram/webhook Endpoint + TelegramService.DeleteWebhook() - api.js: apiTelegramWebhookInfo(), apiTelegramRegisterWebhook(), apiTelegramDeleteWebhook() - Settings-Page: Neue 'Telegram Bot' Section (nur für Admins, nur wenn konfiguriert) - Status abrufen: Webhook-URL, ausstehende Updates, letzter Fehler, Aktiv-Badge - Webhook registrieren: prompt() für Base-URL, POST an Telegram API - Webhook entfernen: Bestätigungsdialog + DELETE - CSS: .telegram-status-box, .tg-status-row, .tg-label, .tg-value, .tg-badge
This commit is contained in:
1 parent
7d237c866a
commit
4ac042e512
6 files changed
+232
-1
No files matched your search
@@ -27,6 +27,9 @@ public static class TelegramEndpoint
|
|||||||
|
|
||||||
// Webhook-Info (Admin)
|
// Webhook-Info (Admin)
|
||||||
app.MapGet("/api/telegram/webhook-info", HandleWebhookInfo);
|
app.MapGet("/api/telegram/webhook-info", HandleWebhookInfo);
|
||||||
|
|
||||||
|
// Webhook löschen (Admin)
|
||||||
|
app.MapDelete("/api/telegram/webhook", HandleDeleteWebhook);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleWebhook(HttpRequest req, HttpResponse res, TelegramService telegram)
|
private static async Task HandleWebhook(HttpRequest req, HttpResponse res, TelegramService telegram)
|
||||||
@@ -136,4 +139,18 @@ public static class TelegramEndpoint
|
|||||||
res.ContentType = "application/json";
|
res.ContentType = "application/json";
|
||||||
await res.WriteAsync(result);
|
await res.WriteAsync(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task HandleDeleteWebhook(HttpRequest req, HttpResponse res, TelegramService telegram)
|
||||||
|
{
|
||||||
|
if (!req.HttpContext.IsAdmin())
|
||||||
|
{
|
||||||
|
res.StatusCode = 403;
|
||||||
|
await res.WriteAsJsonAsync(new { error = "Admin required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await telegram.DeleteWebhook();
|
||||||
|
res.ContentType = "application/json";
|
||||||
|
await res.WriteAsync(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -835,6 +835,15 @@ public class TelegramService
|
|||||||
var resp = await client.GetAsync($"https://api.telegram.org/bot{_botToken}/getWebhookInfo");
|
var resp = await client.GetAsync($"https://api.telegram.org/bot{_botToken}/getWebhookInfo");
|
||||||
return await resp.Content.ReadAsStringAsync();
|
return await resp.Content.ReadAsStringAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> DeleteWebhook()
|
||||||
|
{
|
||||||
|
if (!IsConfigured) return "Telegram Bot nicht konfiguriert.";
|
||||||
|
|
||||||
|
var client = _httpFactory.CreateClient();
|
||||||
|
var resp = await client.PostAsync($"https://api.telegram.org/bot{_botToken}/deleteWebhook", null);
|
||||||
|
return await resp.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenUsage und ToolResult werden aus AnthropicService.cs verwendet
|
// TokenUsage und ToolResult werden aus AnthropicService.cs verwendet
|
||||||
@@ -375,3 +375,62 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--badge-spacing);
|
gap: var(--badge-spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Telegram Settings Status Box ── */
|
||||||
|
|
||||||
|
.telegram-status-box {
|
||||||
|
margin: 0 0 var(--box-spacing) 0;
|
||||||
|
border: var(--border-width) solid var(--f1-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tg-status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: var(--box-spacing);
|
||||||
|
padding: calc(var(--box-padding) * 0.6) var(--box-padding);
|
||||||
|
border-bottom: var(--border-width) solid var(--f1-border);
|
||||||
|
font-size: var(--font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tg-status-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tg-status-row.tg-error {
|
||||||
|
background: color-mix(in srgb, var(--danger) 8%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tg-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 160px;
|
||||||
|
color: var(--text-muted, var(--semi-transparent));
|
||||||
|
opacity: var(--semi-transparent);
|
||||||
|
font-size: calc(var(--font-size) * 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tg-value {
|
||||||
|
flex: 1;
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: var(--font-mono, monospace);
|
||||||
|
font-size: calc(var(--font-size) * 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tg-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 99px;
|
||||||
|
font-family: var(--font-sans, sans-serif);
|
||||||
|
font-size: calc(var(--font-size) * 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tg-badge-ok {
|
||||||
|
background: color-mix(in srgb, var(--success, #22c55e) 15%, transparent);
|
||||||
|
color: var(--success, #22c55e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tg-badge-off {
|
||||||
|
background: color-mix(in srgb, var(--warning, #f59e0b) 15%, transparent);
|
||||||
|
color: var(--warning, #f59e0b);
|
||||||
|
}
|
||||||
@@ -284,6 +284,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="settingsTelegramSection" class="settings-section" style="display:none">
|
||||||
|
<h2>Telegram Bot</h2>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label>Status</label>
|
||||||
|
<span class="setting-hint">Verbindung zum Telegram Bot und Webhook-URL</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<button id="settingsTelegramStatusBtn">Status abrufen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="settingsTelegramStatusBox" class="telegram-status-box" style="display:none"></div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label>Webhook registrieren</label>
|
||||||
|
<span class="setting-hint">Registriert den Webhook bei Telegram (einmalig nach dem Deploy nötig)</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<button id="settingsTelegramRegisterBtn">Registrieren</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label>Webhook entfernen</label>
|
||||||
|
<span class="setting-hint">Deregistriert den Webhook bei Telegram – Bot empfängt dann keine Updates mehr</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<button id="settingsTelegramDeleteBtn" class="danger-btn">Webhook entfernen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<section class="settings-section settings-reset">
|
<section class="settings-section settings-reset">
|
||||||
<button id="settingsResetBtn" class="danger-btn">Alle Einstellungen zurücksetzen</button>
|
<button id="settingsResetBtn" class="danger-btn">Alle Einstellungen zurücksetzen</button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -274,6 +274,28 @@ async function apiTelegramLink(sessionId) {
|
|||||||
return resp.json();
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function apiTelegramWebhookInfo() {
|
||||||
|
const resp = await fetch('/api/telegram/webhook-info');
|
||||||
|
if (!resp.ok) throw new Error('Webhook-Info fehlgeschlagen');
|
||||||
|
return resp.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apiTelegramRegisterWebhook(baseUrl) {
|
||||||
|
const resp = await fetch('/api/telegram/register-webhook', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ baseUrl })
|
||||||
|
});
|
||||||
|
if (!resp.ok) throw new Error('Webhook-Registrierung fehlgeschlagen');
|
||||||
|
return resp.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apiTelegramDeleteWebhook() {
|
||||||
|
const resp = await fetch('/api/telegram/webhook', { method: 'DELETE' });
|
||||||
|
if (!resp.ok) throw new Error('Webhook-Löschung 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');
|
||||||
|
|||||||
@@ -40,10 +40,12 @@ registerPage('settings', {
|
|||||||
const chainMetaSelect = document.getElementById('settingChainMetaDisplay');
|
const chainMetaSelect = document.getElementById('settingChainMetaDisplay');
|
||||||
const resetBtn = document.getElementById('settingsResetBtn');
|
const resetBtn = document.getElementById('settingsResetBtn');
|
||||||
const backupSection = document.getElementById('settingsBackupSection');
|
const backupSection = document.getElementById('settingsBackupSection');
|
||||||
|
const telegramSection = document.getElementById('settingsTelegramSection');
|
||||||
|
|
||||||
// Backup nur für Admins
|
// Backup + Telegram nur für Admins
|
||||||
if (currentUser && currentUser.isAdmin) {
|
if (currentUser && currentUser.isAdmin) {
|
||||||
backupSection.style.display = '';
|
backupSection.style.display = '';
|
||||||
|
initTelegramSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
const s = loadSettings();
|
const s = loadSettings();
|
||||||
@@ -99,6 +101,97 @@ registerPage('settings', {
|
|||||||
toolCallSelect.value = SETTINGS_DEFAULTS.toolCallDisplay;
|
toolCallSelect.value = SETTINGS_DEFAULTS.toolCallDisplay;
|
||||||
chainMetaSelect.value = SETTINGS_DEFAULTS.chainMetaDisplay;
|
chainMetaSelect.value = SETTINGS_DEFAULTS.chainMetaDisplay;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function initTelegramSection() {
|
||||||
|
// Erst prüfen ob Telegram überhaupt konfiguriert ist
|
||||||
|
apiTelegramStatus().then(s => {
|
||||||
|
if (!s.configured) return; // Bot-Token nicht gesetzt → Section versteckt lassen
|
||||||
|
telegramSection.style.display = '';
|
||||||
|
|
||||||
|
const statusBtn = document.getElementById('settingsTelegramStatusBtn');
|
||||||
|
const statusBox = document.getElementById('settingsTelegramStatusBox');
|
||||||
|
const registerBtn = document.getElementById('settingsTelegramRegisterBtn');
|
||||||
|
const deleteBtn = document.getElementById('settingsTelegramDeleteBtn');
|
||||||
|
|
||||||
|
// Status abrufen
|
||||||
|
statusBtn.addEventListener('click', async () => {
|
||||||
|
statusBtn.disabled = true;
|
||||||
|
statusBtn.textContent = 'Wird abgerufen...';
|
||||||
|
statusBox.style.display = 'none';
|
||||||
|
try {
|
||||||
|
const info = await apiTelegramWebhookInfo();
|
||||||
|
const result = info.result || {};
|
||||||
|
const url = result.url || '(kein Webhook registriert)';
|
||||||
|
const pending = result.pending_update_count ?? 0;
|
||||||
|
const lastError = result.last_error_message;
|
||||||
|
let html = `<div class="tg-status-row"><span class="tg-label">Webhook-URL</span><span class="tg-value">${escapeHtml(url)}</span></div>`;
|
||||||
|
html += `<div class="tg-status-row"><span class="tg-label">Ausstehende Updates</span><span class="tg-value">${pending}</span></div>`;
|
||||||
|
if (lastError) {
|
||||||
|
html += `<div class="tg-status-row tg-error"><span class="tg-label">Letzter Fehler</span><span class="tg-value">${escapeHtml(lastError)}</span></div>`;
|
||||||
|
}
|
||||||
|
const active = !!result.url;
|
||||||
|
html += `<div class="tg-status-row"><span class="tg-label">Status</span><span class="tg-value tg-badge ${active ? 'tg-badge-ok' : 'tg-badge-off'}">${active ? '✅ Aktiv' : '⚠️ Nicht registriert'}</span></div>`;
|
||||||
|
statusBox.innerHTML = html;
|
||||||
|
statusBox.style.display = '';
|
||||||
|
} catch (e) {
|
||||||
|
statusBox.innerHTML = `<div class="tg-status-row tg-error"><span class="tg-value">Fehler: ${escapeHtml(e.message)}</span></div>`;
|
||||||
|
statusBox.style.display = '';
|
||||||
|
} finally {
|
||||||
|
statusBtn.disabled = false;
|
||||||
|
statusBtn.textContent = 'Status abrufen';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Webhook registrieren
|
||||||
|
registerBtn.addEventListener('click', async () => {
|
||||||
|
const baseUrl = prompt('Base-URL der App (z.B. https://deine-domain.de):', window.location.origin);
|
||||||
|
if (!baseUrl) return;
|
||||||
|
registerBtn.disabled = true;
|
||||||
|
registerBtn.textContent = 'Wird registriert...';
|
||||||
|
try {
|
||||||
|
const res = await apiTelegramRegisterWebhook(baseUrl.trim());
|
||||||
|
if (res.result && res.result.ok) {
|
||||||
|
alert('✅ Webhook erfolgreich registriert!');
|
||||||
|
} else {
|
||||||
|
alert('Antwort: ' + JSON.stringify(res.result));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Fehler: ' + e.message);
|
||||||
|
} finally {
|
||||||
|
registerBtn.disabled = false;
|
||||||
|
registerBtn.textContent = 'Registrieren';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Webhook löschen
|
||||||
|
deleteBtn.addEventListener('click', async () => {
|
||||||
|
if (!confirm('Webhook wirklich entfernen? Der Bot empfängt dann keine Nachrichten mehr.')) return;
|
||||||
|
deleteBtn.disabled = true;
|
||||||
|
deleteBtn.textContent = 'Wird entfernt...';
|
||||||
|
try {
|
||||||
|
const res = await apiTelegramDeleteWebhook();
|
||||||
|
if (res.ok) {
|
||||||
|
alert('✅ Webhook erfolgreich entfernt.');
|
||||||
|
} else {
|
||||||
|
alert('Antwort: ' + JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Fehler: ' + e.message);
|
||||||
|
} finally {
|
||||||
|
deleteBtn.disabled = false;
|
||||||
|
deleteBtn.textContent = 'Webhook entfernen';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
destroy() {}
|
destroy() {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
return String(str)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
}
|
||||||
Reference in new issue
Block a user