@@ -2355,10 +2372,99 @@ async function testTelefoniCall() {
}
}
+function renderDriftConnectors() {
+ const container = document.getElementById('driftConnectorCards');
+ if (!container) return;
+
+ const connectors = [
+ {
+ key: 'uptime-kuma',
+ title: 'Uptime Kuma',
+ description: 'Opsæt API-adgang til monitoring, alarmsindhentning og statusoplysninger.',
+ badge: 'Aktiv',
+ body: `
+
+
+
+
+
Eksempel: https://status.bmcnetworks.dk
+
+
+
+
+
+
+ Disse værdier gemmes i systemindstillingerne og kan bruges af Drift-modulet til at hente alarmsdata.
+
+
+
+
+
+
+ `
+ },
+ {
+ key: 'uisp',
+ title: 'UISP',
+ description: 'Opsæt API-adgang til UISP, så UISP-enheder kan blive til Drift-alarmer og NOC-status.',
+ badge: 'Ny',
+ body: `
+
+
+
+
+
+
+
+
+
+
+ Disse værdier gemmes i systemindstillingerne og bruges af Drift-modulet til at hente UISP-enhedsstatus.
+
+
+
+
+
+
+
+
Matcher device-navn, source_event_id eller UISP device-id. Blacklisted devices ignoreres ved fremtidige syncs.
+
+
+
+
+
+
+
+ `
+ }
+ ];
+
+ container.innerHTML = connectors.map(connector => `
+
+
+
+
${escapeHtml(connector.title)}
+
${escapeHtml(connector.description)}
+
+
${escapeHtml(connector.badge)}
+
+ ${connector.body}
+
+ `).join('');
+}
+
async function loadSettings() {
try {
- const response = await fetch('/api/v1/settings');
- allSettings = await response.json();
+ const response = await fetch('/api/v1/settings', { credentials: 'include' });
+ if (!response.ok) {
+ throw new Error(await getErrorMessage(response, 'Kunne ikke indlaese indstillinger'));
+ }
+ const payload = await response.json();
+ allSettings = Array.isArray(payload) ? payload : [];
displaySettingsByCategory();
loadTimeMultiplierPresets();
renderTelefoniSettings();
@@ -2368,6 +2474,9 @@ async function loadSettings() {
await loadTagsManagement();
await loadNextcloudInstances();
await loadAnydeskSettings();
+ renderDriftConnectors();
+ await loadUptimeKumaSettings();
+ await loadUISPSettings();
await loadLabelPrinterSettings();
} catch (error) {
console.error('Error loading settings:', error);
@@ -2604,6 +2713,218 @@ async function saveAnydeskSettings() {
}
}
+async function loadUptimeKumaSettings() {
+ const keys = ['drift_uptime_kuma_base_url', 'drift_uptime_kuma_api_key'];
+ try {
+ const results = await Promise.allSettled(
+ keys.map(k => fetch(`/api/v1/settings/${k}`, { credentials: 'include' }).then(r => r.ok ? r.json() : null))
+ );
+ const vals = {};
+ results.forEach((r, i) => { if (r.status === 'fulfilled' && r.value) vals[keys[i]] = r.value.value; });
+
+ document.getElementById('uptimeKumaBaseUrl').value = vals.drift_uptime_kuma_base_url || '';
+ document.getElementById('uptimeKumaApiKey').value = vals.drift_uptime_kuma_api_key || '';
+ } catch (e) {
+ console.warn('Uptime Kuma settings load failed:', e);
+ }
+}
+
+async function saveUptimeKumaSettings() {
+ const baseUrl = (document.getElementById('uptimeKumaBaseUrl').value || '').trim();
+ const apiKey = (document.getElementById('uptimeKumaApiKey').value || '').trim();
+ const statusEl = document.getElementById('uptimeKumaSaveStatus');
+
+ statusEl.textContent = 'Gemmer...';
+ statusEl.className = 'small text-muted';
+
+ const upsertSettingStrict = async (key, value) => {
+ const response = await fetch(`/api/v1/settings/${key}`, {
+ method: 'PUT',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ value: String(value) })
+ });
+
+ if (response.status === 404 || response.status === 405) {
+ const createResponse = await fetch('/api/v1/settings', {
+ method: 'POST',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ key,
+ value: String(value),
+ category: 'integrations',
+ description: key === 'drift_uptime_kuma_base_url' ? 'Base URL for the Uptime Kuma drift connector' : 'API token for the Uptime Kuma drift connector',
+ value_type: 'string',
+ is_public: key === 'drift_uptime_kuma_base_url'
+ })
+ });
+ if (!createResponse.ok) {
+ throw new Error(await getErrorMessage(createResponse, `Kunne ikke gemme ${key}`));
+ }
+ return;
+ }
+
+ if (!response.ok) {
+ throw new Error(await getErrorMessage(response, `Kunne ikke gemme ${key}`));
+ }
+ };
+
+ try {
+ await Promise.all([
+ upsertSettingStrict('drift_uptime_kuma_base_url', baseUrl),
+ upsertSettingStrict('drift_uptime_kuma_api_key', apiKey),
+ ]);
+ statusEl.textContent = '✅ Gemt';
+ statusEl.className = 'small text-success';
+ setTimeout(() => { statusEl.textContent = ''; }, 3000);
+ showNotification('Uptime Kuma indstillinger gemt', 'success');
+ } catch (error) {
+ statusEl.textContent = '❌ Kunne ikke gemme';
+ statusEl.className = 'small text-danger';
+ showNotification('Kunne ikke gemme Uptime Kuma indstillinger', 'error');
+ }
+}
+
+async function loadUISPSettings() {
+ const keys = ['drift_uisp_base_url', 'drift_uisp_api_token', 'drift_device_blacklist'];
+ try {
+ const results = await Promise.allSettled(
+ keys.map(k => fetch(`/api/v1/settings/${k}`, { credentials: 'include' }).then(r => r.ok ? r.json() : null))
+ );
+ const vals = {};
+ results.forEach((r, i) => { if (r.status === 'fulfilled' && r.value) vals[keys[i]] = r.value.value; });
+
+ document.getElementById('uispBaseUrl').value = vals.drift_uisp_base_url || '';
+ document.getElementById('uispApiToken').value = vals.drift_uisp_api_token || '';
+ driftBlacklistItems = parseDriftBlacklistValue(vals.drift_device_blacklist || '[]');
+ renderDriftBlacklistList();
+ } catch (e) {
+ console.warn('UISP settings load failed:', e);
+ }
+}
+
+let driftBlacklistItems = [];
+
+function parseDriftBlacklistValue(rawValue) {
+ const raw = String(rawValue || '').trim();
+ if (!raw) return [];
+ let values = [];
+ try {
+ const parsed = JSON.parse(raw);
+ if (Array.isArray(parsed)) {
+ values = parsed;
+ } else if (typeof parsed === 'string') {
+ values = [parsed];
+ }
+ } catch (e) {
+ values = raw.replaceAll(';', '\n').replaceAll(',', '\n').split('\n');
+ }
+
+ const seen = new Set();
+ const cleaned = [];
+ values.forEach(item => {
+ const normalized = String(item || '').trim().toLowerCase();
+ if (!normalized || seen.has(normalized)) return;
+ seen.add(normalized);
+ cleaned.push(normalized);
+ });
+ return cleaned;
+}
+
+function renderDriftBlacklistList() {
+ const list = document.getElementById('uispBlacklistList');
+ if (!list) return;
+ if (!driftBlacklistItems.length) {
+ list.innerHTML = '
Ingen blacklist entries endnu.';
+ return;
+ }
+ list.innerHTML = driftBlacklistItems.map(item => {
+ const safe = String(item).replace(//g, '>');
+ return `
${safe} `;
+ }).join('');
+}
+
+function addDriftBlacklistItem() {
+ const input = document.getElementById('uispBlacklistInput');
+ if (!input) return;
+ const value = String(input.value || '').trim().toLowerCase();
+ if (!value) return;
+ if (!driftBlacklistItems.includes(value)) {
+ driftBlacklistItems.push(value);
+ }
+ input.value = '';
+ renderDriftBlacklistList();
+}
+
+function removeDriftBlacklistItem(item) {
+ driftBlacklistItems = driftBlacklistItems.filter(v => v !== item);
+ renderDriftBlacklistList();
+}
+
+async function saveUISPSettings() {
+ const baseUrl = (document.getElementById('uispBaseUrl').value || '').trim();
+ const apiToken = (document.getElementById('uispApiToken').value || '').trim();
+ const statusEl = document.getElementById('uispSaveStatus');
+
+ statusEl.textContent = 'Gemmer...';
+ statusEl.className = 'small text-muted';
+
+ const upsertSettingStrict = async (key, value) => {
+ const response = await fetch(`/api/v1/settings/${key}`, {
+ method: 'PUT',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ value: String(value) })
+ });
+
+ if (response.status === 404 || response.status === 405) {
+ const createResponse = await fetch('/api/v1/settings', {
+ method: 'POST',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ key,
+ value: String(value),
+ category: 'integrations',
+ description: key === 'drift_uisp_base_url'
+ ? 'Base URL for the UISP drift connector'
+ : key === 'drift_uisp_api_token'
+ ? 'API token for the UISP drift connector'
+ : 'JSON array of Drift device identifiers/names to ignore during sync',
+ value_type: 'string',
+ is_public: key === 'drift_uisp_base_url'
+ })
+ });
+ if (!createResponse.ok) {
+ throw new Error(await getErrorMessage(createResponse, `Kunne ikke gemme ${key}`));
+ }
+ return;
+ }
+
+ if (!response.ok) {
+ throw new Error(await getErrorMessage(response, `Kunne ikke gemme ${key}`));
+ }
+ };
+
+ try {
+ const blacklistJson = JSON.stringify(parseDriftBlacklistValue(driftBlacklistItems));
+ await Promise.all([
+ upsertSettingStrict('drift_uisp_base_url', baseUrl),
+ upsertSettingStrict('drift_uisp_api_token', apiToken),
+ upsertSettingStrict('drift_device_blacklist', blacklistJson),
+ ]);
+ statusEl.textContent = '✅ Gemt';
+ statusEl.className = 'small text-success';
+ setTimeout(() => { statusEl.textContent = ''; }, 3000);
+ showNotification('UISP indstillinger gemt', 'success');
+ } catch (error) {
+ statusEl.textContent = '❌ Kunne ikke gemme';
+ statusEl.className = 'small text-danger';
+ showNotification('Kunne ikke gemme UISP indstillinger', 'error');
+ }
+}
+
async function loadLabelPrinterSettings() {
const keys = [
'label_printer_enabled',
@@ -2653,6 +2974,7 @@ async function saveLabelPrinterSettings() {
const putSettingStrict = async (key, value) => {
const response = await fetch(`/api/v1/settings/${key}`, {
method: 'PUT',
+ credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value: String(value) })
});
@@ -2964,17 +3286,29 @@ async function updateSetting(key, value) {
try {
const response = await fetch(`/api/v1/settings/${key}`, {
method: 'PUT',
+ credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value })
});
-
- if (response.ok) {
- // Show success toast
+
+ if (response.status === 404 || response.status === 405) {
+ const createResponse = await fetch('/api/v1/settings', {
+ method: 'POST',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ key, value, category: 'general', value_type: 'string', is_public: false })
+ });
+ if (!createResponse.ok) {
+ throw new Error(await getErrorMessage(createResponse, 'Kunne ikke opdatere indstilling'));
+ }
+ } else if (response.ok) {
console.log(`✅ Updated ${key}`);
+ } else {
+ throw new Error(await getErrorMessage(response, 'Kunne ikke opdatere indstilling'));
}
} catch (error) {
console.error('Error updating setting:', error);
- alert('Kunne ikke opdatere indstilling');
+ alert(error.message || 'Kunne ikke opdatere indstilling');
}
}
@@ -5387,11 +5721,17 @@ function createToastContainer() {
async function loadPipelineStages() {
try {
const response = await fetch('/api/v1/pipeline/stages');
- const stages = await response.json();
- pipelineStagesCache = stages || [];
+ if (!response.ok) {
+ throw new Error(await getErrorMessage(response, 'Kunne ikke indlaese pipeline stages'));
+ }
+ const payload = await response.json();
+ const stages = Array.isArray(payload) ? payload : [];
+ pipelineStagesCache = stages;
renderPipelineStages(pipelineStagesCache);
} catch (error) {
console.error('Error loading pipeline stages:', error);
+ pipelineStagesCache = [];
+ renderPipelineStages(pipelineStagesCache);
}
}
@@ -5932,6 +6272,15 @@ document.addEventListener('DOMContentLoaded', () => {