`;
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();
});