diff --git a/options.js b/options.js index ab32cdb..7c389c0 100644 --- a/options.js +++ b/options.js @@ -1,382 +1,501 @@ -document.addEventListener('DOMContentLoaded', function() { - const serviceForm = document.getElementById('add-service-form'); - const intervalSelect = document.getElementById('check-interval'); - const notifyOnlineCheckbox = document.getElementById('notify-online'); - const exportBtn = document.getElementById('export-services'); - const importBtn = document.getElementById('import-services'); - const importFileInput = document.getElementById('import-file'); - const servicesListStat = document.getElementById('services-list-stat'); - const emptyState = document.getElementById('empty-state'); - const searchInput = document.getElementById('services-search'); - const MAX_POPUP_SERVERS = 8; - let currentChart = null; - - // NEU: Discord Webhook Input - const discordWebhookInput = document.getElementById('discord-webhook-url'); - - // --- Tab-Logik --- - const tabButtons = document.querySelectorAll('.tab-btn'); - const tabPanels = document.querySelectorAll('.tab-panel'); - tabButtons.forEach(button => { - button.addEventListener('click', () => { - const targetTab = button.getAttribute('data-tab'); - tabButtons.forEach(btn => btn.classList.remove('active')); - tabPanels.forEach(panel => panel.classList.remove('active')); - button.classList.add('active'); - document.getElementById(`${targetTab}-panel`).classList.add('active'); - }); - }); - - // --- Utilities --- - function getAllData(callback) { - chrome.storage.sync.get({ services: [], popupServers: [] }, function(syncData) { - chrome.storage.local.get({ serviceStatus: {}, history: {} }, function(localData) { - callback(syncData.services || [], syncData.popupServers || [], localData.serviceStatus || {}, localData.history || {}); - }); - }); - } - - // --- NEU: Popup-Server-Auswahl rendern --- - function renderPopupServerSelection(allServices) { - const container = document.getElementById('popup-server-list'); - const infoElement = document.getElementById('selection-info'); - container.innerHTML = ''; // Leeren - - if (allServices.length === 0) { - infoElement.textContent = 'Bitte fügen Sie zuerst Server hinzu.'; - return; - } - - chrome.storage.sync.get({ popupServers: [] }, (data) => { - const selectedServers = data.popupServers || []; - - // Info-Text aktualisieren - infoElement.textContent = `${selectedServers.length} von ${MAX_POPUP_SERVERS} Servern für das Popup ausgewählt.`; - - allServices.forEach(service => { - const serverItem = document.createElement('div'); - serverItem.className = 'popup-server-item'; - - const isSelected = selectedServers.some(s => s.name === service.name && s.adresse === service.adresse); - - serverItem.innerHTML = ` - -
-
${escapeHtml(service.name)}
-
${escapeHtml(service.adresse || '')}
-
- `; - - const checkbox = serverItem.querySelector('input[type="checkbox"]'); - checkbox.addEventListener('change', () => { - updatePopupServerSelection(service, checkbox.checked, allServices); - }); - - container.appendChild(serverItem); - }); - }); - } - - // --- NEU: Auswahl aktualisieren --- - function updatePopupServerSelection(service, isSelected, allServices) { - chrome.storage.sync.get({ popupServers: [] }, (data) => { - let selectedServers = data.popupServers || []; - - if (isSelected) { - // Verhindere, dass mehr als MAX_POPUP_SERVERS ausgewählt werden - if (selectedServers.length >= MAX_POPUP_SERVERS) { - alert(`Sie können maximal ${MAX_POPUP_SERVERS} Server auswählen.`); - checkbox.checked = false; // Haken entfernen - return; - } - selectedServers.push(service); - } else { - selectedServers = selectedServers.filter(s => !(s.name === service.name && s.adresse === service.adresse)); - } - - chrome.storage.sync.set({ popupServers: selectedServers }, () => { - // Info-Text und Checkboxen aktualisieren - renderPopupServerSelection(allServices); - }); - }); - } - - - // --- Services rendern --- - function renderServices(filter = '') { - getAllData((services, popupServers, statuses, history) => { - servicesListStat.innerHTML = ''; - const filtered = services.filter(s => s.name.toLowerCase().includes(filter.toLowerCase())); - if (filtered.length === 0) { - emptyState.style.display = services.length === 0 ? 'block' : 'none'; - if (services.length === 0) return; - } else { - emptyState.style.display = 'none'; - } - - filtered.forEach((service, index) => { - const statusObj = statuses[service.name] || {}; - const st = statusObj.status || 'unknown'; - const resp = typeof statusObj.responseTime === 'number' ? `${statusObj.responseTime} ms` : ''; - - const li = document.createElement('li'); - li.className = 'service-stat-item'; - li.dataset.index = index; - - li.innerHTML = ` -
-
${escapeHtml(service.name)}
-
${escapeHtml(service.adresse || '')}
-
-
-
- - ${resp} -
-
- - -
-
- `; - - li.querySelector('.service-name').addEventListener('click', () => { - showStatsForService(service); - highlightSelection(li); - }); - - li.querySelector('.service-name').addEventListener('keydown', (e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - li.querySelector('.service-name').click(); - } - }); - - li.querySelector('.icon-btn.edit').addEventListener('click', (e) => { - e.stopPropagation(); - enterEditMode(li, service, services); - }); - - li.querySelector('.icon-btn.delete').addEventListener('click', (e) => { - e.stopPropagation(); - if (confirm(`Server "${service.name}" wirklich löschen?`)) { - services.splice(index, 1); - chrome.storage.sync.set({ services }, () => { - renderServices(searchInput.value.trim()); - renderPopupServerSelection(services); // Auch die Auswahl neu rendern - }); - } - }); - - servicesListStat.appendChild(li); - }); - }); - } - - function highlightSelection(itemEl) { - document.querySelectorAll('.service-stat-item').forEach(it => it.classList.remove('selected')); - itemEl.classList.add('selected'); - } - - function enterEditMode(li, service, allServices) { - li.classList.add('editing'); - li.innerHTML = ` -
- - -
-
- - -
- `; - - 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 zu den abgerufenen Daten hinzufügen - chrome.storage.sync.get({ checkInterval: 1, notifyOnline: false, discordWebhookUrl: '' }, function(data) { - intervalSelect.value = data.checkInterval; - notifyOnlineCheckbox.checked = data.notifyOnline; - discordWebhookInput.value = data.discordWebhookUrl || ''; // NEU - }); - } - 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() }); - }); - - // --- Import/Export --- - exportBtn.addEventListener('click', () => { - chrome.storage.sync.get({ services: [] }, (data) => { - const dataStr = JSON.stringify(data.services, 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-services.json'; - 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 importedServices = JSON.parse(e.target.result); - if (Array.isArray(importedServices)) { - chrome.storage.sync.set({ services: importedServices }, () => { - renderServices(searchInput.value.trim()); - renderPopupServerSelection(importedServices); // Auch die Auswahl neu rendern - }); - } - else { alert('Ungültiges Dateiformat.'); } - } catch (error) { alert('Fehler beim Lesen der Datei.'); } - }; - 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 || []); - }); +document.addEventListener('DOMContentLoaded', function() { + const serviceForm = document.getElementById('add-service-form'); + const intervalSelect = document.getElementById('check-interval'); + const notifyOnlineCheckbox = document.getElementById('notify-online'); + const exportBtn = document.getElementById('export-services'); + const importBtn = document.getElementById('import-services'); + const importFileInput = document.getElementById('import-file'); + const servicesListStat = document.getElementById('services-list-stat'); + const emptyState = document.getElementById('empty-state'); + const searchInput = document.getElementById('services-search'); + const MAX_POPUP_SERVERS = 8; + let currentChart = null; + + // NEU: Discord Webhook Input + const discordWebhookInput = document.getElementById('discord-webhook-url'); + // NEU: Telegram Inputs + const telegramBotTokenInput = document.getElementById('telegram-bot-token'); + const telegramChatIdInput = document.getElementById('telegram-chat-id'); + + // --- Tab-Logik --- + const tabButtons = document.querySelectorAll('.tab-btn'); + const tabPanels = document.querySelectorAll('.tab-panel'); + tabButtons.forEach(button => { + button.addEventListener('click', () => { + const targetTab = button.getAttribute('data-tab'); + tabButtons.forEach(btn => btn.classList.remove('active')); + tabPanels.forEach(panel => panel.classList.remove('active')); + button.classList.add('active'); + document.getElementById(`${targetTab}-panel`).classList.add('active'); + }); + }); + + // --- Utilities --- + function getAllData(callback) { + chrome.storage.sync.get({ services: [], popupServers: [] }, function(syncData) { + chrome.storage.local.get({ serviceStatus: {}, history: {} }, function(localData) { + callback(syncData.services || [], syncData.popupServers || [], localData.serviceStatus || {}, localData.history || {}); + }); + }); + } + + // --- NEU: Popup-Server-Auswahl rendern --- + function renderPopupServerSelection(allServices) { + const container = document.getElementById('popup-server-list'); + const infoElement = document.getElementById('selection-info'); + container.innerHTML = ''; // Leeren + + if (allServices.length === 0) { + infoElement.textContent = 'Bitte fügen Sie zuerst Server hinzu.'; + return; + } + + chrome.storage.sync.get({ popupServers: [] }, (data) => { + const selectedServers = data.popupServers || []; + + // Info-Text aktualisieren + infoElement.textContent = `${selectedServers.length} von ${MAX_POPUP_SERVERS} Servern für das Popup ausgewählt.`; + + allServices.forEach(service => { + const serverItem = document.createElement('div'); + serverItem.className = 'popup-server-item'; + + const isSelected = selectedServers.some(s => s.name === service.name && s.adresse === service.adresse); + + serverItem.innerHTML = ` + +
+
${escapeHtml(service.name)}
+
${escapeHtml(service.adresse || '')}
+
+ `; + + const checkbox = serverItem.querySelector('input[type="checkbox"]'); + checkbox.addEventListener('change', () => { + updatePopupServerSelection(service, checkbox.checked, allServices); + }); + + container.appendChild(serverItem); + }); + }); + } + + // --- NEU: Auswahl aktualisieren --- + function updatePopupServerSelection(service, isSelected, allServices) { + chrome.storage.sync.get({ popupServers: [] }, (data) => { + let selectedServers = data.popupServers || []; + + if (isSelected) { + // Verhindere, dass mehr als MAX_POPUP_SERVERS ausgewählt werden + if (selectedServers.length >= MAX_POPUP_SERVERS) { + alert(`Sie können maximal ${MAX_POPUP_SERVERS} Server auswählen.`); + // Finde die Checkbox und setze den Haken zurück + const checkboxId = `popup-server-${service.name.replace(/\s+/g, '-')}`; + const checkbox = document.getElementById(checkboxId); + if(checkbox) checkbox.checked = false; + return; + } + selectedServers.push(service); + } else { + selectedServers = selectedServers.filter(s => !(s.name === service.name && s.adresse === service.adresse)); + } + + chrome.storage.sync.set({ popupServers: selectedServers }, () => { + // Info-Text und Checkboxen aktualisieren + renderPopupServerSelection(allServices); + }); + }); + } + + + // --- Services rendern --- + function renderServices(filter = '') { + getAllData((services, popupServers, statuses, history) => { + servicesListStat.innerHTML = ''; + const filtered = services.filter(s => s.name.toLowerCase().includes(filter.toLowerCase())); + if (filtered.length === 0) { + emptyState.style.display = services.length === 0 ? 'block' : 'none'; + if (services.length === 0) return; + } else { + emptyState.style.display = 'none'; + } + + filtered.forEach((service, index) => { + const statusObj = statuses[service.name] || {}; + const st = statusObj.status || 'unknown'; + const resp = typeof statusObj.responseTime === 'number' ? `${statusObj.responseTime} ms` : ''; + + const li = document.createElement('li'); + li.className = 'service-stat-item'; + li.dataset.index = index; + + li.innerHTML = ` +
+
${escapeHtml(service.name)}
+
${escapeHtml(service.adresse || '')}
+
+
+
+ + ${resp} +
+
+ + +
+
+ `; + + li.querySelector('.service-name').addEventListener('click', () => { + showStatsForService(service); + highlightSelection(li); + }); + + li.querySelector('.service-name').addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + li.querySelector('.service-name').click(); + } + }); + + li.querySelector('.icon-btn.edit').addEventListener('click', (e) => { + e.stopPropagation(); + enterEditMode(li, service, services); + }); + + li.querySelector('.icon-btn.delete').addEventListener('click', (e) => { + e.stopPropagation(); + if (confirm(`Server "${service.name}" wirklich löschen?`)) { + services.splice(index, 1); + chrome.storage.sync.set({ services }, () => { + renderServices(searchInput.value.trim()); + renderPopupServerSelection(services); // Auch die Auswahl neu rendern + }); + } + }); + + servicesListStat.appendChild(li); + }); + }); + } + + function highlightSelection(itemEl) { + document.querySelectorAll('.service-stat-item').forEach(it => it.classList.remove('selected')); + itemEl.classList.add('selected'); + } + + function enterEditMode(li, service, allServices) { + li.classList.add('editing'); + li.innerHTML = ` +
+ + +
+
+ + +
+ `; + + 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