+ `;
+
+ const saveBtn = li.querySelector('.btn-save');
+ const cancelBtn = li.querySelector('.btn-cancel');
+
+ cancelBtn.addEventListener('click', (ev) => {
+ ev.stopPropagation();
+ renderServices(searchInput.value.trim());
+ });
+
+ saveBtn.addEventListener('click', (ev) => {
+ ev.stopPropagation();
+ const newName = li.querySelector('.edit-name').value.trim();
+ const newAdresse = li.querySelector('.edit-adresse').value.trim();
+ if (!newName || !newAdresse) { alert('Name und Adresse dürfen nicht leer sein.'); return; }
+
+ chrome.storage.sync.get({ services: [] }, function(data) {
+ const services = data.services || [];
+ const idx = services.findIndex(s => s.name === service.name && (s.adresse || '') === (service.adresse || ''));
+ if (idx === -1) {
+ alert('Fehler: Dienst nicht gefunden.');
+ renderServices(searchInput.value.trim());
+ return;
+ }
+ services[idx].name = newName;
+ services[idx].adresse = newAdresse;
+ chrome.storage.sync.set({ services }, () => {
+ renderServices(searchInput.value.trim());
+ renderPopupServerSelection(services); // Auch die Auswahl neu rendern
+ });
+ });
+ });
+ }
+
+ // --- Add service ---
+ function addService(name, adresse) {
+ if (!name || !adresse) return;
+ chrome.storage.sync.get({ services: [] }, function(data) {
+ const newService = { name, adresse };
+ const updatedServices = [...data.services, newService];
+ chrome.storage.sync.set({ services: updatedServices }, () => {
+ renderServices(searchInput.value.trim());
+ renderPopupServerSelection(updatedServices); // Auch die Auswahl neu rendern
+ });
+ });
+ }
+
+ serviceForm.addEventListener('submit', function(event) {
+ event.preventDefault();
+ const nameInput = document.getElementById('service-name');
+ const protocolSelect = document.getElementById('service-protocol');
+ const domainInput = document.getElementById('service-domain');
+ const fullAdresse = protocolSelect.value + domainInput.value.trim();
+ addService(nameInput.value.trim(), fullAdresse);
+ serviceForm.reset();
+ nameInput.focus();
+ });
+
+ // --- Settings load/save ---
+ function loadSettings() {
+ // NEU: discordWebhookUrl, telegramBotToken und telegramChatId zu den abgerufenen Daten hinzufügen
+ chrome.storage.sync.get({ checkInterval: 1, notifyOnline: false, discordWebhookUrl: '', telegramBotToken: '', telegramChatId: '' }, function(data) {
+ intervalSelect.value = data.checkInterval;
+ notifyOnlineCheckbox.checked = data.notifyOnline;
+ discordWebhookInput.value = data.discordWebhookUrl || '';
+ telegramBotTokenInput.value = data.telegramBotToken || '';
+ telegramChatIdInput.value = data.telegramChatId || '';
+ });
+ }
+ intervalSelect.addEventListener('change', () => {
+ const newInterval = parseFloat(intervalSelect.value);
+ chrome.storage.sync.set({ checkInterval: newInterval }, () => { chrome.runtime.sendMessage({ type: 'updateInterval' }); });
+ });
+ notifyOnlineCheckbox.addEventListener('change', () => { chrome.storage.sync.set({ notifyOnline: notifyOnlineCheckbox.checked }); });
+
+ // NEU: Event Listener für Discord Webhook URL
+ discordWebhookInput.addEventListener('change', () => {
+ chrome.storage.sync.set({ discordWebhookUrl: discordWebhookInput.value.trim() });
+ });
+
+ // NEU: Event Listener für Telegram
+ telegramBotTokenInput.addEventListener('change', () => {
+ chrome.storage.sync.set({ telegramBotToken: telegramBotTokenInput.value.trim() });
+ });
+ telegramChatIdInput.addEventListener('change', () => {
+ chrome.storage.sync.set({ telegramChatId: telegramChatIdInput.value.trim() });
+ });
+
+
+ // --- ERWEITERTES Import/Export ---
+ exportBtn.addEventListener('click', () => {
+ // Rufe alle relevanten Daten aus dem Speicher ab, inklusive der neuen Telegram-Einstellungen
+ chrome.storage.sync.get({
+ services: [],
+ popupServers: [],
+ checkInterval: 1,
+ notifyOnline: false,
+ discordWebhookUrl: '',
+ telegramBotToken: '',
+ telegramChatId: ''
+ }, (data) => {
+ // Erstelle einen String aus den Daten für die JSON-Datei
+ const dataStr = JSON.stringify(data, null, 2);
+ const blob = new Blob([dataStr], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'uptime-monitor-backup.json'; // Passenderer Dateiname
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ });
+ });
+
+ importBtn.addEventListener('click', () => importFileInput.click());
+ importFileInput.addEventListener('change', (event) => {
+ const file = event.target.files[0];
+ if (!file) return;
+
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ try {
+ const importedData = JSON.parse(e.target.result);
+
+ // Prüfe, ob die importierten Daten ein Objekt sind
+ if (typeof importedData === 'object' && importedData !== null) {
+ // Setze alle importierten Daten auf einmal
+ chrome.storage.sync.set(importedData, () => {
+ alert('Backup erfolgreich wiederhergestellt! Die Seite wird neu geladen, um alle Änderungen zu übernehmen.');
+ // Nach dem Import ist es am sichersten, die Seite neu zu laden
+ location.reload();
+ });
+ } else {
+ alert('Ungültiges Dateiformat. Die Datei muss eine gültige Backup-Datei sein.');
+ }
+ } catch (error) {
+ console.error('Fehler beim Verarbeiten der Import-Datei:', error);
+ alert('Fehler beim Lesen der Datei. Bitte stellen Sie sicher, dass es sich um eine gültige JSON-Datei handelt.');
+ }
+ };
+ reader.readAsText(file);
+ });
+
+
+ // --- Statistik-Anzeige ---
+ function showStatsForService(service) {
+ if (!service) return;
+ document.getElementById('no-service-selected').style.display = 'none';
+ const chartCanvas = document.getElementById('uptimeChart');
+ chartCanvas.style.display = 'block';
+
+ chrome.storage.local.get({ history: {} }, (data) => {
+ const history = data.history[service.name] || {};
+ const labels = Object.keys(history).sort().slice(-48);
+ const uptimeData = labels.map(label => {
+ const h = history[label];
+ return h.checks > 0 ? (h.up_checks / h.checks * 100).toFixed(2) : 0;
+ });
+
+ if (currentChart) currentChart.destroy();
+ const ctx = chartCanvas.getContext('2d');
+
+ if (labels.length === 0) {
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ ctx.font = "16px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto";
+ ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--text-secondary');
+ ctx.textAlign = 'center';
+ ctx.fillText('Noch keine Daten verfügbar.', ctx.canvas.width / 2, ctx.canvas.height / 2);
+ return;
+ }
+
+ const formattedLabels = labels.map(l => {
+ const parts = l.split(' ');
+ return parts.length === 2 ? parts[1] : l;
+ });
+
+ currentChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: formattedLabels,
+ datasets: [{
+ label: `Uptime für ${service.name} (%)`,
+ data: uptimeData,
+ borderColor: 'var(--accent-color)',
+ backgroundColor: 'rgba(24, 119, 242, 0.1)',
+ fill: true,
+ tension: 0.3
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ y: { beginAtZero: true, max: 100, ticks: { callback: value => value + '%' } }
+ },
+ plugins: { legend: { display: false } }
+ }
+ });
+ });
+ }
+
+ // --- Search ---
+ searchInput.addEventListener('input', () => {
+ renderServices(searchInput.value.trim());
+ });
+
+ // --- Helpers ---
+ function escapeHtml(str) { if (!str) return ''; return String(str).replace(/[&<>"']/g, s => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[s])); }
+ function escapeAttr(s) { return (s||'').replace(/"/g, '"'); }
+
+ // --- Init ---
+ loadSettings();
+ renderServices();
+ // Initiales Rendern der Popup-Auswahl
+ chrome.storage.sync.get({ services: [] }, (data) => {
+ renderPopupServerSelection(data.services || []);
+ });
+
+ // --- ROBUSTE: Gitea Repository Widget ---
+ // Führt mehrere Suchen durch und kombiniert die Ergebnisse.
+ function fetchAndDisplayGiteaRepos() {
+ const list = document.getElementById('gitea-repos-list');
+ const loadingIndicator = document.getElementById('gitea-repos-loading');
+ const errorIndicator = document.getElementById('gitea-repos-error');
+
+ const searchKeywords = ["Gitea", "Themes", "Server", "Minecraft", "Webseite", "Wordpress", "Plugin", "chrome-erweiterung"];
+
+ loadingIndicator.style.display = 'block';
+ list.style.display = 'none';
+ errorIndicator.style.display = 'none';
+
+ // Erstelle eine Liste von Promises, eine für jede Suchanfrage
+ const searchPromises = searchKeywords.map(keyword => {
+ const apiUrl = `https://git.viper.ipv64.net/api/v1/repos/search?q=${encodeURIComponent(keyword)}&limit=50&sort=updated&order=desc`;
+ return fetch(apiUrl).then(response => {
+ if (!response.ok) {
+ // Wenn eine Anfrage fehlschlägt, geben wir ein leeres Array zurück, um die anderen nicht zu blockieren
+ console.warn(`Suche nach "${keyword}" fehlgeschlagen:`, response.status);
+ return { data: [] }; // Gib eine leere Antwort-Struktur zurück
+ }
+ return response.json();
+ });
+ });
+
+ // Führe alle Suchanfragen parallel aus
+ Promise.all(searchPromises)
+ .then(results => {
+ // Kombiniere alle Ergebnisse aus den einzelnen Suchen
+ const allRepos = results.flatMap(result => result.data || []);
+
+ // Entferne Duplikate anhand der einzigartigen ID des Repositories
+ const uniqueReposMap = new Map();
+ allRepos.forEach(repo => {
+ uniqueReposMap.set(repo.id, repo);
+ });
+ const uniqueRepos = Array.from(uniqueReposMap.values());
+
+ // Sortiere die kombinierten Repositories nach dem letzten Update-Datum
+ uniqueRepos.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
+
+ // Begrenze die Ergebnisse auf die 5 neuesten
+ const finalRepos = uniqueRepos.slice(0, 5);
+
+ loadingIndicator.style.display = 'none';
+ list.innerHTML = '';
+
+ if (finalRepos.length > 0) {
+ list.style.display = 'block';
+ finalRepos.forEach(repo => {
+ const li = document.createElement('li');
+ const link = document.createElement('a');
+ link.href = repo.html_url;
+ link.textContent = repo.name;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ li.appendChild(link);
+ list.appendChild(li);
+ });
+ } else {
+ list.style.display = 'block';
+ list.innerHTML = '
Keine passenden Repositories gefunden.
';
+ }
+ })
+ .catch(error => {
+ console.error('Fehler beim Abrufen der Gitea Repositories:', error);
+ loadingIndicator.style.display = 'none';
+ errorIndicator.style.display = 'block';
+ });
+ }
+
+ // Rufe die neue Funktion auf, wenn die Seite geladen wird.
+ fetchAndDisplayGiteaRepos();
});
\ No newline at end of file