Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. script Portainer Api V3.3 - inkl. html Tabelle

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.5k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    2.1k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    16
    1
    2.7k

script Portainer Api V3.3 - inkl. html Tabelle

Geplant Angeheftet Gesperrt Verschoben Skripten / Logik
blocklyjavascriptmonitoring
14 Beiträge 3 Kommentatoren 601 Aufrufe 3 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • ilovegymI Online
    ilovegymI Online
    ilovegym
    schrieb am zuletzt editiert von ilovegym
    #1

    Edit: Header, Text, Scripte :). vielen Dank an alle die mir hier geholfen haben!

    Hallo,
    wie bekomme ich die Daten aus Portainer in den iobroker?

    Edit:
    Hier das, was chatgpt nach einigen Versuchen mir ausgegeben hat, das Javascript fragt die Portainer-Api in regelmaessigen Abstaenden ab, man kann Docker Container starten/stoppen, bekommt bei Aenderungen eine Msg per Discord, und es werden Cpu, Ram, Uptime, Image, IP, Port angezeigt:

    // 📦 Portainer Statusboard Version 3.3
    // by Ilovegym
    // 📜 Änderungsprotokoll
    // - Version 3.3: Mobile Ansicht – CPU/RAM ein-/ausblendbar
    // - Version 3.2: Mobile Ansicht mit Symbolstatus & 2-zeiligem Layout konfigurierbar
    // - Version 3.1: Mobiler Dashboard-Datenpunkt konfigurierbar
    // - Version 3.0: Mobiles Dashboard in Datenpunkt StatusMobile
    // - Version 2.7: Persistenter Theme-Switch
    // - Version 2.6: Persistente Filter und zusätzliche Filterfelder (CPU, RAM, Alter)
    // - Version 2.5: Filter für laufende Container eingebaut
    // - Version 2.4: "Erstellt" zeigt nun Alter in Tagen an
    // - Version 2.3: Alphabetische Standard-Sortierung
    // - Version 2.2: Warn-Icons, Farb-Balken für CPU/RAM, letzter Restart-Zeitpunkt
    // - Version 2.1: CPU und RAM korrekt aus Stats übernommen
    // - Version 2.0: Hinzugefügt: Restarted-Spalte, Tooltip mit IP, Theme-Switch, Sortierung
    // - Version 1.x: Basisfunktion: Containerdatenpunkte, Discord/Telegram Benachrichtigungen, Statusboard
    const exec = require('child_process').exec;
    
    // 🔧 KONFIGURATION
    const mobileLayoutColumns = 1; // 1 = eine Spalte, 2 = zwei Spalten in mobiler Ansicht
    
    const mobileColumnOrder = [
        'name', 'state', 'cpu', 'mem', 'uptime'
    ]; // Spaltenreihenfolge in der mobilen Ansicht
    
    const tableColumnOrder = [
        'name', 'cpu', 'mem', 'state', 'uptime', 'restartedRecently', 'restartAt', 'image', 'created', 'ports'
    ]; // Reihenfolge der Spalten im Statusboard
    
    const username = 'admin';
    const password = 'supergeheimespassword!';
    const portainerHost = 'http://10.10.2.10:9000';
    const endpointId = 2;  //default 1
    const datapointBase = '0_userdata.0.Portainer.Containers.';
    const statusBoardDP = '0_userdata.0.Portainer.Containers.Statusboard';
    const updateIntervalMinutes = 3;
    const mobileStatusDP = '0_userdata.0.Portainer.Containers.StatusMobile';
    const mobileUseSymbol = true;       // true = ✅/❌, false = 'running'
    const mobileShowCPU = true;         // true = CPU in mobiler Ansicht anzeigen
    const mobileShowRAM = true;         // true = RAM in mobiler Ansicht anzeigen
    const mobileTwoLineLayout = true;   // true = Name/Status oben, Details unten
    const discordDP = 'discord.0.xxxx.send'; //bitte anpassen
    
    const discordToggleDP = '0_userdata.0.Portainer.Notify.Discord';
    const telegramToggleDP = '0_userdata.0.Portainer.Notify.Telegram';
    const telegramInstance = 'telegram.0';
    
    // Initiale Schalter
    if (!existsState(discordToggleDP)) {
        createState(discordToggleDP, true, { type: 'boolean', name: 'Discord aktivieren', read: true, write: true });
    }
    if (!existsState(telegramToggleDP)) {
        createState(telegramToggleDP, false, { type: 'boolean', name: 'Telegram aktivieren', read: true, write: true });
    }
    
    function getTokenAndContainers() {
        const loginCmd = `curl -s -H "Content-Type: application/json" -X POST -d '{"Username":"${username}","Password":"${password}"}' ${portainerHost}/api/auth`;
        exec(loginCmd, (error, stdout) => {
            if (error) return log(`❌ Token-Fehler: ${error}`, 'error');
            try {
                const token = JSON.parse(stdout).jwt;
                if (!token) return log('❌ Kein Token erhalten', 'error');
                getContainerList(token);
            } catch (e) {
                log(`❌ Token-Parsing-Fehler: ${e.message}`, 'error');
            }
        });
    }
    
    function getContainerList(token) {
        const url = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/json?all=1`;
        const cmd = `curl -s -H "Authorization: Bearer ${token}" "${url}"`;
        exec(cmd, (error, stdout) => {
            if (error) return log(`❌ Fehler beim Containerabruf: ${error}`, 'error');
    
            try {
                const containers = JSON.parse(stdout);
                const containerList = [];
    
                containers.forEach(container => {
                    const id = container.Id;
                    const name = container.Names[0].replace(/^\//, '');
                    const dp = `${datapointBase}${name}`;
    
                    const image = container.Image;
                    const createdDate = new Date(container.Created * 1000);
                    const createdDaysAgo = Math.floor((Date.now() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
                    const created = `${createdDate.toLocaleString('de-DE', { hour12: false })} (${createdDaysAgo} Tage alt)`;
                    const ports = (container.Ports || []).map(p => `${p.IP || '0.0.0.0'}:${p.PublicPort}->${p.PrivatePort}/${p.Type}`).join(', ');
                    const ip = container.NetworkSettings?.Networks?.bridge?.IPAddress || 'n/a';
    
                    createState(`${dp}.image`, image, true);
                    setState(`${dp}.image`, image, true);
                    createState(`${dp}.created`, created, true);
                    setState(`${dp}.created`, created, true);
                    createState(`${dp}.ports`, ports, true);
                    setState(`${dp}.ports`, ports, true);
                    createState(`${dp}.ip`, ip, true);
                    setState(`${dp}.ip`, ip, true);
                    createState(`${dp}.control`, false, { type: 'boolean', role: 'button', write: true, read: false });
    
                    const detailUrl = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/${id}/json`;
                    const detailCmd = `curl -s -H "Authorization: Bearer ${token}" "${detailUrl}"`;
                    exec(detailCmd, (err, detailOut) => {
                        if (err) return log(`❌ Fehler bei Details für ${name}: ${err}`, 'warn');
                        try {
                            const detail = JSON.parse(detailOut);
                            const isRunning = detail?.State?.Running === true;
                            const state = isRunning ? 'running' : 'exited';
                            const startedAt = new Date(isRunning ? detail.State.StartedAt : 0);
                            const now = new Date();
                            const uptimeMs = isRunning ? now - startedAt : 0;
                            const uptimeText = formatUptime(uptimeMs);
    
                            const stateDP = `${dp}.state`;
                            getStateAsync(stateDP).then(oldState => {
                                if (oldState && oldState.val !== state) {
                                    const msg = `🔔 Container \`${name}\` Status: \`${oldState.val}\` → \`${state}\``;
                                    sendDiscordMessage(msg);
                                    sendTelegramMessage(msg);
                                }
                            });
    
                            createState(stateDP, state, true);
                            setState(stateDP, state, true);
                            createState(`${dp}.uptimeText`, uptimeText, true);
                            setState(`${dp}.uptimeText`, uptimeText, true);
    
                            const statsCmd = `curl -s -H "Authorization: Bearer ${token}" "${portainerHost}/api/endpoints/${endpointId}/docker/containers/${id}/stats?stream=false"`;
                            exec(statsCmd, (e, statsOut) => {
                                if (e) return;
                                try {
                                    const stats = JSON.parse(statsOut);
                                    const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
                                    const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
                                    const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * stats.cpu_stats.online_cpus * 100 : 0;
                                    const memMB = Math.round((stats.memory_stats.usage || 0) / 1024 / 1024);
    
                                    createState(`${dp}.cpu`, Math.round(cpuPercent * 10) / 10, true);
                                    setState(`${dp}.cpu`, Math.round(cpuPercent * 10) / 10, true);
                                    createState(`${dp}.mem`, memMB, true);
                                    setState(`${dp}.mem`, memMB, true);
    
                                    const cpuSafe = Math.round(cpuPercent * 10) / 10;
                                    const memSafe = memMB;
                                    const restartedRecently = isRunning && (uptimeMs < 2 * 60 * 1000);
                                    const restartAt = isRunning ? startedAt.toLocaleString('de-DE', { hour12: false }) : '-';
    containerList.push({ name, state, uptime: uptimeText, cpu: cpuSafe, mem: memSafe, image, ports, created, ip, restartedRecently, restartAt });
    
                                    setState(statusBoardDP, buildHTML(containerList), true);
                                    setState(mobileStatusDP, buildMobileHTML(containerList), true);
                                } catch (e) {
                                    log(`⚠️ Stats-Parsing-Fehler ${name}: ${e.message}`, 'warn');
                                }
                            });
    
                            
    
                        } catch (e) {
                            log(`❌ Detail-Parsing-Fehler ${name}: ${e.message}`, 'warn');
                        }
                    });
                });
            } catch (err) {
                log(`❌ Containerdaten-Fehler: ${err.message}`, 'error');
            }
        });
    }
    
    function formatUptime(ms) {
        const totalSeconds = Math.floor(ms / 1000);
        const days = Math.floor(totalSeconds / 86400);
        const hours = Math.floor((totalSeconds % 86400) / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        let str = '';
        if (days > 0) str += `${days}d `;
        if (days > 0 || hours > 0) str += `${hours}h `;
        str += `${minutes}min`;
        return str.trim();
    }
    
    on(new RegExp(`${datapointBase}(.*)\.control`), function (obj) {
        if (obj.state.val !== true) return;
        const name = obj.id.split('.')[4];
        const loginCmd = `curl -s -H "Content-Type: application/json" -X POST -d '{"Username":"${username}","Password":"${password}"}' ${portainerHost}/api/auth`;
        exec(loginCmd, (error, stdout) => {
            if (error) return log(`❌ Loginfehler: ${error}`, 'error');
            try {
                const token = JSON.parse(stdout).jwt;
                const listCmd = `curl -s -H "Authorization: Bearer ${token}" "${portainerHost}/api/endpoints/${endpointId}/docker/containers/json?all=1"`;
                exec(listCmd, (err, out) => {
                    const list = JSON.parse(out);
                    const container = list.find(c => c.Names[0].replace(/^\//, '') === name);
                    if (!container) return log(`⚠️ Container ${name} nicht gefunden`, 'warn');
                    const action = container.State === 'running' ? 'stop' : 'start';
                    const actionUrl = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/${container.Id}/${action}`;
                    const actionCmd = `curl -s -H "Authorization: Bearer ${token}" -X POST "${actionUrl}"`;
                    exec(actionCmd, () => {
                        log(`✅ Container ${name} ${action} ausgeführt`);
                        setTimeout(() => getTokenAndContainers(), 3000);
                        setTimeout(() => setState(obj.id, false, true), 500);
                    });
                });
            } catch (e) {
                log(`❌ Steuerungsfehler: ${e.message}`, 'error');
            }
        });
    });
    
    function sendDiscordMessage(message) {
        const discordEnabled = getState(discordToggleDP)?.val === true;
        if (discordEnabled && existsState(discordDP)) {
            setState(discordDP, message);
            log(`📤 Discord: ${message}`);
        } else {
            log(`ℹ️ Discord deaktiviert oder nicht verfügbar`, 'info');
        }
    }
    
    function sendTelegramMessage(message) {
        const telegramEnabled = getState(telegramToggleDP)?.val === true;
        if (telegramEnabled) {
            sendTo(telegramInstance, 'send', { text: message });
            log(`📤 Telegram: ${message}`);
        } else {
            log(`ℹ️ Telegram deaktiviert`, 'info');
        }
    }
    
    function buildHTML(list) {
        list.sort((a, b) => a.name.localeCompare(b.name));
        let html = `<b style="color:#ffffff;">🧾 Docker Statusboard</b><br>
        <label for="runningFilter" style="color:#ffffff;">🚦 Nur laufende anzeigen</label>
        <input type="checkbox" id="runningFilter" onchange="filterRunning()" style="margin-bottom:10px;">
        <label for="themeToggle" style="color:#ffffff;">🌗 Theme</label>
        <input type="checkbox" id="themeToggle" checked onchange="toggleTheme()" style="margin-bottom: 10px;"><br>
        <table id="dockerStatus" border="1" cellspacing="0" cellpadding="6"
            style="font-family:Arial; font-size:13px; border-collapse:collapse;
                   text-align:left; background:#1e1e1e; color:#ffffff; border-color:#444; width:100%;">
            <thead>
            <tr style="background:#333;">
                <th onclick="sortTable(0)">Name</th>
                <th onclick="sortTable(1)">CPU</th>
                <th onclick="sortTable(2)">RAM</th>
                <th onclick="sortTable(3)">Status</th>
                <th onclick="sortTable(4)">Uptime</th>
                <th onclick="sortTable(5)">Restarted</th>
                <th onclick="sortTable(6)">Restart-Zeit</th>
                <th onclick="sortTable(7)">Image</th>
                <th onclick="sortTable(8)">Erstellt</th>
                <th onclick="sortTable(9)">Ports</th>
            </tr>
            </thead><tbody>`;
    
        for (let c of list) {
            html += `<tr style="background:${c.state === 'running' ? '#2d572c' : '#571b1b'};" title="IP: ${c.ip}">
                <td>${c.name}</td>
                <td style="color:${c.cpu > 50 ? '#ff5555' : '#cccccc'}">${c.cpu}</td>
                <td style="color:${c.mem > 500 ? '#ff8800' : '#cccccc'}">${c.mem}</td>
                <td>${c.state === 'running' ? '✅' : '❌'} ${c.state}</td>
                <td>${c.uptime}</td>
                <td style="color: ${c.restartedRecently ? '#ffaa00' : '#cccccc'};">${c.restartedRecently ? 'ja' : 'nein'}</td>
                <td>${c.restartAt}</td>
                <td>${c.image}</td>
                <td>${c.created}</td>
                <td>${c.ports}</td>
            </tr>`;
        }
    
        html += `</tbody></table>
        <script>
            function filterRunning() {
                var table = document.getElementById("dockerStatus");
                var rows = table.getElementsByTagName("tr");
                var onlyRunning = document.getElementById("runningFilter").checked;
                for (var i = 1; i < rows.length; i++) {
                    var stateCell = rows[i].getElementsByTagName("td")[3];
                    if (!stateCell) continue;
                    var isRunning = stateCell.textContent.includes("running");
                    rows[i].style.display = (onlyRunning && !isRunning) ? "none" : "";
                }
            }
            function sortTable(n) {
                var table = document.getElementById("dockerStatus");
                var switching = true;
                var dir = "asc";
                var switchcount = 0;
                while (switching) {
                    switching = false;
                    var rows = table.rows;
                    for (var i = 1; i < rows.length - 1; i++) {
                        var shouldSwitch = false;
                        var x = rows[i].getElementsByTagName("TD")[n];
                        var y = rows[i + 1].getElementsByTagName("TD")[n];
                        var xContent = x.textContent || x.innerText;
                        var yContent = y.textContent || y.innerText;
                        var xNum = parseFloat(xContent);
                        var yNum = parseFloat(yContent);
                        var isNumber = !isNaN(xNum) && !isNaN(yNum);
                        if (isNumber) {
                            if ((dir === "asc" && xNum > yNum) || (dir === "desc" && xNum < yNum)) {
                                shouldSwitch = true;
                                break;
                            }
                        } else {
                            if ((dir === "asc" && xContent.toLowerCase() > yContent.toLowerCase()) ||
                                (dir === "desc" && xContent.toLowerCase() < yContent.toLowerCase())) {
                                shouldSwitch = true;
                                break;
                            }
                        }
                    }
                    if (shouldSwitch) {
                        rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
                        switching = true;
                        switchcount++;
                    } else {
                        if (switchcount === 0 && dir === "asc") {
                            dir = "desc";
                            switching = true;
                        }
                    }
                }
            }
        function toggleTheme() {
                var table = document.getElementById("dockerStatus");
                var isDark = document.getElementById("themeToggle").checked;
                table.style.background = isDark ? "#1e1e1e" : "#ffffff";
                table.style.color = isDark ? "#ffffff" : "#000000";
                var th = table.getElementsByTagName("th");
                for (var i = 0; i < th.length; i++) {
                    th[i].style.background = isDark ? "#333" : "#dddddd";
                }
                localStorage.setItem("themeDark", isDark);
            }
            window.addEventListener("load", () => {
                document.getElementById("themeToggle").checked = localStorage.getItem("themeDark") === "true";
                toggleTheme();
            });
            }
        </script>`;
        return html;
    }
    
    // Start und Zyklus
    getTokenAndContainers();
    schedule(`*/${updateIntervalMinutes} * * * *`, getTokenAndContainers);
    
    function buildMobileHTML(list) {
        list.sort((a, b) => a.name.localeCompare(b.name));
        let html = `<div style="font-family:Arial; font-size:14px;"><b>📱 Docker Übersicht</b><br><br>`;
    
        const columnWidth = mobileLayoutColumns === 2 ? '48%' : '100%';
        const wrapperStyle = mobileLayoutColumns === 2 ? 'display:flex; flex-wrap:wrap; justify-content:space-between;' : '';
    
        html += `<div style="${wrapperStyle}">`;
    
        for (let c of list) {
            const color = c.state === 'running' ? '#2d572c' : '#571b1b';
            const cpu = parseFloat(c.cpu);
            const mem = parseFloat(c.mem);
            const statusText = mobileUseSymbol ? (c.state === 'running' ? '✅' : '❌') : c.state;
            let metricsLine = '';
            if (mobileShowCPU) metricsLine += ` CPU: ${cpu}%`;
            if (mobileShowRAM) metricsLine += ` RAM: ${mem} MB`;
    
            let card = '';
            if (mobileTwoLineLayout) {
                card = `<div style="margin-bottom:12px; padding:8px; border-radius:8px; background:${color}; width:${columnWidth}; box-sizing:border-box;">
                    <b>${c.name}</b><br>
                    <small>${statusText}${metricsLine}</small><br>
                    <small>Uptime: ${c.uptime}</small>
                </div>`;
            } else {
                card = `<div style="margin-bottom:12px; padding:8px; border-radius:8px; background:${color}; width:${columnWidth}; box-sizing:border-box;">
                    <b>${c.name}</b><br>
                    <small>Status: ${statusText}${metricsLine}, Uptime: ${c.uptime}</small>
                </div>`;
            }
            html += card;
        }
    
        html += `</div></div>`;
        return html;
    }
    
    
    

    Edit2:
    Screenshot von den Objects:
    Screenshot 2025-08-01 at 14.38.20.png

    Edit3:
    Script um eine html Tabelle aus den States zu erzeugen, Datenpunkte und Container bitte anpassen:
    wurde alles in ein Script gepackt, ist einfacher.

    Screenshot: Tabelle
    Screenshot 2025-08-01 at 16.15.07.png

    Screenshot: Mobile Ansicht, 2 Spaltig (konfigurierbar)
    Screenshot 2025-08-04 at 20.53.06.png

    CodierknechtC David G.D 3 Antworten Letzte Antwort
    0
    • ilovegymI ilovegym

      Edit: Header, Text, Scripte :). vielen Dank an alle die mir hier geholfen haben!

      Hallo,
      wie bekomme ich die Daten aus Portainer in den iobroker?

      Edit:
      Hier das, was chatgpt nach einigen Versuchen mir ausgegeben hat, das Javascript fragt die Portainer-Api in regelmaessigen Abstaenden ab, man kann Docker Container starten/stoppen, bekommt bei Aenderungen eine Msg per Discord, und es werden Cpu, Ram, Uptime, Image, IP, Port angezeigt:

      // 📦 Portainer Statusboard Version 3.3
      // by Ilovegym
      // 📜 Änderungsprotokoll
      // - Version 3.3: Mobile Ansicht – CPU/RAM ein-/ausblendbar
      // - Version 3.2: Mobile Ansicht mit Symbolstatus & 2-zeiligem Layout konfigurierbar
      // - Version 3.1: Mobiler Dashboard-Datenpunkt konfigurierbar
      // - Version 3.0: Mobiles Dashboard in Datenpunkt StatusMobile
      // - Version 2.7: Persistenter Theme-Switch
      // - Version 2.6: Persistente Filter und zusätzliche Filterfelder (CPU, RAM, Alter)
      // - Version 2.5: Filter für laufende Container eingebaut
      // - Version 2.4: "Erstellt" zeigt nun Alter in Tagen an
      // - Version 2.3: Alphabetische Standard-Sortierung
      // - Version 2.2: Warn-Icons, Farb-Balken für CPU/RAM, letzter Restart-Zeitpunkt
      // - Version 2.1: CPU und RAM korrekt aus Stats übernommen
      // - Version 2.0: Hinzugefügt: Restarted-Spalte, Tooltip mit IP, Theme-Switch, Sortierung
      // - Version 1.x: Basisfunktion: Containerdatenpunkte, Discord/Telegram Benachrichtigungen, Statusboard
      const exec = require('child_process').exec;
      
      // 🔧 KONFIGURATION
      const mobileLayoutColumns = 1; // 1 = eine Spalte, 2 = zwei Spalten in mobiler Ansicht
      
      const mobileColumnOrder = [
          'name', 'state', 'cpu', 'mem', 'uptime'
      ]; // Spaltenreihenfolge in der mobilen Ansicht
      
      const tableColumnOrder = [
          'name', 'cpu', 'mem', 'state', 'uptime', 'restartedRecently', 'restartAt', 'image', 'created', 'ports'
      ]; // Reihenfolge der Spalten im Statusboard
      
      const username = 'admin';
      const password = 'supergeheimespassword!';
      const portainerHost = 'http://10.10.2.10:9000';
      const endpointId = 2;  //default 1
      const datapointBase = '0_userdata.0.Portainer.Containers.';
      const statusBoardDP = '0_userdata.0.Portainer.Containers.Statusboard';
      const updateIntervalMinutes = 3;
      const mobileStatusDP = '0_userdata.0.Portainer.Containers.StatusMobile';
      const mobileUseSymbol = true;       // true = ✅/❌, false = 'running'
      const mobileShowCPU = true;         // true = CPU in mobiler Ansicht anzeigen
      const mobileShowRAM = true;         // true = RAM in mobiler Ansicht anzeigen
      const mobileTwoLineLayout = true;   // true = Name/Status oben, Details unten
      const discordDP = 'discord.0.xxxx.send'; //bitte anpassen
      
      const discordToggleDP = '0_userdata.0.Portainer.Notify.Discord';
      const telegramToggleDP = '0_userdata.0.Portainer.Notify.Telegram';
      const telegramInstance = 'telegram.0';
      
      // Initiale Schalter
      if (!existsState(discordToggleDP)) {
          createState(discordToggleDP, true, { type: 'boolean', name: 'Discord aktivieren', read: true, write: true });
      }
      if (!existsState(telegramToggleDP)) {
          createState(telegramToggleDP, false, { type: 'boolean', name: 'Telegram aktivieren', read: true, write: true });
      }
      
      function getTokenAndContainers() {
          const loginCmd = `curl -s -H "Content-Type: application/json" -X POST -d '{"Username":"${username}","Password":"${password}"}' ${portainerHost}/api/auth`;
          exec(loginCmd, (error, stdout) => {
              if (error) return log(`❌ Token-Fehler: ${error}`, 'error');
              try {
                  const token = JSON.parse(stdout).jwt;
                  if (!token) return log('❌ Kein Token erhalten', 'error');
                  getContainerList(token);
              } catch (e) {
                  log(`❌ Token-Parsing-Fehler: ${e.message}`, 'error');
              }
          });
      }
      
      function getContainerList(token) {
          const url = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/json?all=1`;
          const cmd = `curl -s -H "Authorization: Bearer ${token}" "${url}"`;
          exec(cmd, (error, stdout) => {
              if (error) return log(`❌ Fehler beim Containerabruf: ${error}`, 'error');
      
              try {
                  const containers = JSON.parse(stdout);
                  const containerList = [];
      
                  containers.forEach(container => {
                      const id = container.Id;
                      const name = container.Names[0].replace(/^\//, '');
                      const dp = `${datapointBase}${name}`;
      
                      const image = container.Image;
                      const createdDate = new Date(container.Created * 1000);
                      const createdDaysAgo = Math.floor((Date.now() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
                      const created = `${createdDate.toLocaleString('de-DE', { hour12: false })} (${createdDaysAgo} Tage alt)`;
                      const ports = (container.Ports || []).map(p => `${p.IP || '0.0.0.0'}:${p.PublicPort}->${p.PrivatePort}/${p.Type}`).join(', ');
                      const ip = container.NetworkSettings?.Networks?.bridge?.IPAddress || 'n/a';
      
                      createState(`${dp}.image`, image, true);
                      setState(`${dp}.image`, image, true);
                      createState(`${dp}.created`, created, true);
                      setState(`${dp}.created`, created, true);
                      createState(`${dp}.ports`, ports, true);
                      setState(`${dp}.ports`, ports, true);
                      createState(`${dp}.ip`, ip, true);
                      setState(`${dp}.ip`, ip, true);
                      createState(`${dp}.control`, false, { type: 'boolean', role: 'button', write: true, read: false });
      
                      const detailUrl = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/${id}/json`;
                      const detailCmd = `curl -s -H "Authorization: Bearer ${token}" "${detailUrl}"`;
                      exec(detailCmd, (err, detailOut) => {
                          if (err) return log(`❌ Fehler bei Details für ${name}: ${err}`, 'warn');
                          try {
                              const detail = JSON.parse(detailOut);
                              const isRunning = detail?.State?.Running === true;
                              const state = isRunning ? 'running' : 'exited';
                              const startedAt = new Date(isRunning ? detail.State.StartedAt : 0);
                              const now = new Date();
                              const uptimeMs = isRunning ? now - startedAt : 0;
                              const uptimeText = formatUptime(uptimeMs);
      
                              const stateDP = `${dp}.state`;
                              getStateAsync(stateDP).then(oldState => {
                                  if (oldState && oldState.val !== state) {
                                      const msg = `🔔 Container \`${name}\` Status: \`${oldState.val}\` → \`${state}\``;
                                      sendDiscordMessage(msg);
                                      sendTelegramMessage(msg);
                                  }
                              });
      
                              createState(stateDP, state, true);
                              setState(stateDP, state, true);
                              createState(`${dp}.uptimeText`, uptimeText, true);
                              setState(`${dp}.uptimeText`, uptimeText, true);
      
                              const statsCmd = `curl -s -H "Authorization: Bearer ${token}" "${portainerHost}/api/endpoints/${endpointId}/docker/containers/${id}/stats?stream=false"`;
                              exec(statsCmd, (e, statsOut) => {
                                  if (e) return;
                                  try {
                                      const stats = JSON.parse(statsOut);
                                      const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
                                      const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
                                      const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * stats.cpu_stats.online_cpus * 100 : 0;
                                      const memMB = Math.round((stats.memory_stats.usage || 0) / 1024 / 1024);
      
                                      createState(`${dp}.cpu`, Math.round(cpuPercent * 10) / 10, true);
                                      setState(`${dp}.cpu`, Math.round(cpuPercent * 10) / 10, true);
                                      createState(`${dp}.mem`, memMB, true);
                                      setState(`${dp}.mem`, memMB, true);
      
                                      const cpuSafe = Math.round(cpuPercent * 10) / 10;
                                      const memSafe = memMB;
                                      const restartedRecently = isRunning && (uptimeMs < 2 * 60 * 1000);
                                      const restartAt = isRunning ? startedAt.toLocaleString('de-DE', { hour12: false }) : '-';
      containerList.push({ name, state, uptime: uptimeText, cpu: cpuSafe, mem: memSafe, image, ports, created, ip, restartedRecently, restartAt });
      
                                      setState(statusBoardDP, buildHTML(containerList), true);
                                      setState(mobileStatusDP, buildMobileHTML(containerList), true);
                                  } catch (e) {
                                      log(`⚠️ Stats-Parsing-Fehler ${name}: ${e.message}`, 'warn');
                                  }
                              });
      
                              
      
                          } catch (e) {
                              log(`❌ Detail-Parsing-Fehler ${name}: ${e.message}`, 'warn');
                          }
                      });
                  });
              } catch (err) {
                  log(`❌ Containerdaten-Fehler: ${err.message}`, 'error');
              }
          });
      }
      
      function formatUptime(ms) {
          const totalSeconds = Math.floor(ms / 1000);
          const days = Math.floor(totalSeconds / 86400);
          const hours = Math.floor((totalSeconds % 86400) / 3600);
          const minutes = Math.floor((totalSeconds % 3600) / 60);
          let str = '';
          if (days > 0) str += `${days}d `;
          if (days > 0 || hours > 0) str += `${hours}h `;
          str += `${minutes}min`;
          return str.trim();
      }
      
      on(new RegExp(`${datapointBase}(.*)\.control`), function (obj) {
          if (obj.state.val !== true) return;
          const name = obj.id.split('.')[4];
          const loginCmd = `curl -s -H "Content-Type: application/json" -X POST -d '{"Username":"${username}","Password":"${password}"}' ${portainerHost}/api/auth`;
          exec(loginCmd, (error, stdout) => {
              if (error) return log(`❌ Loginfehler: ${error}`, 'error');
              try {
                  const token = JSON.parse(stdout).jwt;
                  const listCmd = `curl -s -H "Authorization: Bearer ${token}" "${portainerHost}/api/endpoints/${endpointId}/docker/containers/json?all=1"`;
                  exec(listCmd, (err, out) => {
                      const list = JSON.parse(out);
                      const container = list.find(c => c.Names[0].replace(/^\//, '') === name);
                      if (!container) return log(`⚠️ Container ${name} nicht gefunden`, 'warn');
                      const action = container.State === 'running' ? 'stop' : 'start';
                      const actionUrl = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/${container.Id}/${action}`;
                      const actionCmd = `curl -s -H "Authorization: Bearer ${token}" -X POST "${actionUrl}"`;
                      exec(actionCmd, () => {
                          log(`✅ Container ${name} ${action} ausgeführt`);
                          setTimeout(() => getTokenAndContainers(), 3000);
                          setTimeout(() => setState(obj.id, false, true), 500);
                      });
                  });
              } catch (e) {
                  log(`❌ Steuerungsfehler: ${e.message}`, 'error');
              }
          });
      });
      
      function sendDiscordMessage(message) {
          const discordEnabled = getState(discordToggleDP)?.val === true;
          if (discordEnabled && existsState(discordDP)) {
              setState(discordDP, message);
              log(`📤 Discord: ${message}`);
          } else {
              log(`ℹ️ Discord deaktiviert oder nicht verfügbar`, 'info');
          }
      }
      
      function sendTelegramMessage(message) {
          const telegramEnabled = getState(telegramToggleDP)?.val === true;
          if (telegramEnabled) {
              sendTo(telegramInstance, 'send', { text: message });
              log(`📤 Telegram: ${message}`);
          } else {
              log(`ℹ️ Telegram deaktiviert`, 'info');
          }
      }
      
      function buildHTML(list) {
          list.sort((a, b) => a.name.localeCompare(b.name));
          let html = `<b style="color:#ffffff;">🧾 Docker Statusboard</b><br>
          <label for="runningFilter" style="color:#ffffff;">🚦 Nur laufende anzeigen</label>
          <input type="checkbox" id="runningFilter" onchange="filterRunning()" style="margin-bottom:10px;">
          <label for="themeToggle" style="color:#ffffff;">🌗 Theme</label>
          <input type="checkbox" id="themeToggle" checked onchange="toggleTheme()" style="margin-bottom: 10px;"><br>
          <table id="dockerStatus" border="1" cellspacing="0" cellpadding="6"
              style="font-family:Arial; font-size:13px; border-collapse:collapse;
                     text-align:left; background:#1e1e1e; color:#ffffff; border-color:#444; width:100%;">
              <thead>
              <tr style="background:#333;">
                  <th onclick="sortTable(0)">Name</th>
                  <th onclick="sortTable(1)">CPU</th>
                  <th onclick="sortTable(2)">RAM</th>
                  <th onclick="sortTable(3)">Status</th>
                  <th onclick="sortTable(4)">Uptime</th>
                  <th onclick="sortTable(5)">Restarted</th>
                  <th onclick="sortTable(6)">Restart-Zeit</th>
                  <th onclick="sortTable(7)">Image</th>
                  <th onclick="sortTable(8)">Erstellt</th>
                  <th onclick="sortTable(9)">Ports</th>
              </tr>
              </thead><tbody>`;
      
          for (let c of list) {
              html += `<tr style="background:${c.state === 'running' ? '#2d572c' : '#571b1b'};" title="IP: ${c.ip}">
                  <td>${c.name}</td>
                  <td style="color:${c.cpu > 50 ? '#ff5555' : '#cccccc'}">${c.cpu}</td>
                  <td style="color:${c.mem > 500 ? '#ff8800' : '#cccccc'}">${c.mem}</td>
                  <td>${c.state === 'running' ? '✅' : '❌'} ${c.state}</td>
                  <td>${c.uptime}</td>
                  <td style="color: ${c.restartedRecently ? '#ffaa00' : '#cccccc'};">${c.restartedRecently ? 'ja' : 'nein'}</td>
                  <td>${c.restartAt}</td>
                  <td>${c.image}</td>
                  <td>${c.created}</td>
                  <td>${c.ports}</td>
              </tr>`;
          }
      
          html += `</tbody></table>
          <script>
              function filterRunning() {
                  var table = document.getElementById("dockerStatus");
                  var rows = table.getElementsByTagName("tr");
                  var onlyRunning = document.getElementById("runningFilter").checked;
                  for (var i = 1; i < rows.length; i++) {
                      var stateCell = rows[i].getElementsByTagName("td")[3];
                      if (!stateCell) continue;
                      var isRunning = stateCell.textContent.includes("running");
                      rows[i].style.display = (onlyRunning && !isRunning) ? "none" : "";
                  }
              }
              function sortTable(n) {
                  var table = document.getElementById("dockerStatus");
                  var switching = true;
                  var dir = "asc";
                  var switchcount = 0;
                  while (switching) {
                      switching = false;
                      var rows = table.rows;
                      for (var i = 1; i < rows.length - 1; i++) {
                          var shouldSwitch = false;
                          var x = rows[i].getElementsByTagName("TD")[n];
                          var y = rows[i + 1].getElementsByTagName("TD")[n];
                          var xContent = x.textContent || x.innerText;
                          var yContent = y.textContent || y.innerText;
                          var xNum = parseFloat(xContent);
                          var yNum = parseFloat(yContent);
                          var isNumber = !isNaN(xNum) && !isNaN(yNum);
                          if (isNumber) {
                              if ((dir === "asc" && xNum > yNum) || (dir === "desc" && xNum < yNum)) {
                                  shouldSwitch = true;
                                  break;
                              }
                          } else {
                              if ((dir === "asc" && xContent.toLowerCase() > yContent.toLowerCase()) ||
                                  (dir === "desc" && xContent.toLowerCase() < yContent.toLowerCase())) {
                                  shouldSwitch = true;
                                  break;
                              }
                          }
                      }
                      if (shouldSwitch) {
                          rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
                          switching = true;
                          switchcount++;
                      } else {
                          if (switchcount === 0 && dir === "asc") {
                              dir = "desc";
                              switching = true;
                          }
                      }
                  }
              }
          function toggleTheme() {
                  var table = document.getElementById("dockerStatus");
                  var isDark = document.getElementById("themeToggle").checked;
                  table.style.background = isDark ? "#1e1e1e" : "#ffffff";
                  table.style.color = isDark ? "#ffffff" : "#000000";
                  var th = table.getElementsByTagName("th");
                  for (var i = 0; i < th.length; i++) {
                      th[i].style.background = isDark ? "#333" : "#dddddd";
                  }
                  localStorage.setItem("themeDark", isDark);
              }
              window.addEventListener("load", () => {
                  document.getElementById("themeToggle").checked = localStorage.getItem("themeDark") === "true";
                  toggleTheme();
              });
              }
          </script>`;
          return html;
      }
      
      // Start und Zyklus
      getTokenAndContainers();
      schedule(`*/${updateIntervalMinutes} * * * *`, getTokenAndContainers);
      
      function buildMobileHTML(list) {
          list.sort((a, b) => a.name.localeCompare(b.name));
          let html = `<div style="font-family:Arial; font-size:14px;"><b>📱 Docker Übersicht</b><br><br>`;
      
          const columnWidth = mobileLayoutColumns === 2 ? '48%' : '100%';
          const wrapperStyle = mobileLayoutColumns === 2 ? 'display:flex; flex-wrap:wrap; justify-content:space-between;' : '';
      
          html += `<div style="${wrapperStyle}">`;
      
          for (let c of list) {
              const color = c.state === 'running' ? '#2d572c' : '#571b1b';
              const cpu = parseFloat(c.cpu);
              const mem = parseFloat(c.mem);
              const statusText = mobileUseSymbol ? (c.state === 'running' ? '✅' : '❌') : c.state;
              let metricsLine = '';
              if (mobileShowCPU) metricsLine += ` CPU: ${cpu}%`;
              if (mobileShowRAM) metricsLine += ` RAM: ${mem} MB`;
      
              let card = '';
              if (mobileTwoLineLayout) {
                  card = `<div style="margin-bottom:12px; padding:8px; border-radius:8px; background:${color}; width:${columnWidth}; box-sizing:border-box;">
                      <b>${c.name}</b><br>
                      <small>${statusText}${metricsLine}</small><br>
                      <small>Uptime: ${c.uptime}</small>
                  </div>`;
              } else {
                  card = `<div style="margin-bottom:12px; padding:8px; border-radius:8px; background:${color}; width:${columnWidth}; box-sizing:border-box;">
                      <b>${c.name}</b><br>
                      <small>Status: ${statusText}${metricsLine}, Uptime: ${c.uptime}</small>
                  </div>`;
              }
              html += card;
          }
      
          html += `</div></div>`;
          return html;
      }
      
      
      

      Edit2:
      Screenshot von den Objects:
      Screenshot 2025-08-01 at 14.38.20.png

      Edit3:
      Script um eine html Tabelle aus den States zu erzeugen, Datenpunkte und Container bitte anpassen:
      wurde alles in ein Script gepackt, ist einfacher.

      Screenshot: Tabelle
      Screenshot 2025-08-01 at 16.15.07.png

      Screenshot: Mobile Ansicht, 2 Spaltig (konfigurierbar)
      Screenshot 2025-08-04 at 20.53.06.png

      CodierknechtC Online
      CodierknechtC Online
      Codierknecht
      Developer Most Active
      schrieb am zuletzt editiert von Codierknecht
      #2

      @ilovegym sagte in script Dockermon Api:

      wie zerlege ich denn am besten das json

      Ich mache das erstmal immer mit dem "JSON Path Finder":

      7244da4a-b42f-4251-bfed-087876c54f96-image.png

      vom Dockermon-Container

      Geht das nicht auch direkt mit dem Portainer-API?

      a3848f38-9e4f-4d27-95e5-b2761f89e336-image.png

      vor allem, wenn ein Container weg oder dazu kommt.. ?

      In Zabbix geht das mit JSONPath, da kann man nach bestimmten Einträgen suchen.
      Geht sowas nicht auch mit JSONATA? Da ist vermutlich @mickym der Fachmann ;-)

      "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." (Martin Fowler, "Refactoring")

      Proxmox 9.1.1 LXC|8 GB|Core i7-6700
      HmIP|ZigBee|Tasmota|Unifi
      Zabbix Certified Specialist
      Konnte ich Dir helfen? Dann benutze bitte das Voting unten rechts im Beitrag

      1 Antwort Letzte Antwort
      1
      • ilovegymI ilovegym

        Edit: Header, Text, Scripte :). vielen Dank an alle die mir hier geholfen haben!

        Hallo,
        wie bekomme ich die Daten aus Portainer in den iobroker?

        Edit:
        Hier das, was chatgpt nach einigen Versuchen mir ausgegeben hat, das Javascript fragt die Portainer-Api in regelmaessigen Abstaenden ab, man kann Docker Container starten/stoppen, bekommt bei Aenderungen eine Msg per Discord, und es werden Cpu, Ram, Uptime, Image, IP, Port angezeigt:

        // 📦 Portainer Statusboard Version 3.3
        // by Ilovegym
        // 📜 Änderungsprotokoll
        // - Version 3.3: Mobile Ansicht – CPU/RAM ein-/ausblendbar
        // - Version 3.2: Mobile Ansicht mit Symbolstatus & 2-zeiligem Layout konfigurierbar
        // - Version 3.1: Mobiler Dashboard-Datenpunkt konfigurierbar
        // - Version 3.0: Mobiles Dashboard in Datenpunkt StatusMobile
        // - Version 2.7: Persistenter Theme-Switch
        // - Version 2.6: Persistente Filter und zusätzliche Filterfelder (CPU, RAM, Alter)
        // - Version 2.5: Filter für laufende Container eingebaut
        // - Version 2.4: "Erstellt" zeigt nun Alter in Tagen an
        // - Version 2.3: Alphabetische Standard-Sortierung
        // - Version 2.2: Warn-Icons, Farb-Balken für CPU/RAM, letzter Restart-Zeitpunkt
        // - Version 2.1: CPU und RAM korrekt aus Stats übernommen
        // - Version 2.0: Hinzugefügt: Restarted-Spalte, Tooltip mit IP, Theme-Switch, Sortierung
        // - Version 1.x: Basisfunktion: Containerdatenpunkte, Discord/Telegram Benachrichtigungen, Statusboard
        const exec = require('child_process').exec;
        
        // 🔧 KONFIGURATION
        const mobileLayoutColumns = 1; // 1 = eine Spalte, 2 = zwei Spalten in mobiler Ansicht
        
        const mobileColumnOrder = [
            'name', 'state', 'cpu', 'mem', 'uptime'
        ]; // Spaltenreihenfolge in der mobilen Ansicht
        
        const tableColumnOrder = [
            'name', 'cpu', 'mem', 'state', 'uptime', 'restartedRecently', 'restartAt', 'image', 'created', 'ports'
        ]; // Reihenfolge der Spalten im Statusboard
        
        const username = 'admin';
        const password = 'supergeheimespassword!';
        const portainerHost = 'http://10.10.2.10:9000';
        const endpointId = 2;  //default 1
        const datapointBase = '0_userdata.0.Portainer.Containers.';
        const statusBoardDP = '0_userdata.0.Portainer.Containers.Statusboard';
        const updateIntervalMinutes = 3;
        const mobileStatusDP = '0_userdata.0.Portainer.Containers.StatusMobile';
        const mobileUseSymbol = true;       // true = ✅/❌, false = 'running'
        const mobileShowCPU = true;         // true = CPU in mobiler Ansicht anzeigen
        const mobileShowRAM = true;         // true = RAM in mobiler Ansicht anzeigen
        const mobileTwoLineLayout = true;   // true = Name/Status oben, Details unten
        const discordDP = 'discord.0.xxxx.send'; //bitte anpassen
        
        const discordToggleDP = '0_userdata.0.Portainer.Notify.Discord';
        const telegramToggleDP = '0_userdata.0.Portainer.Notify.Telegram';
        const telegramInstance = 'telegram.0';
        
        // Initiale Schalter
        if (!existsState(discordToggleDP)) {
            createState(discordToggleDP, true, { type: 'boolean', name: 'Discord aktivieren', read: true, write: true });
        }
        if (!existsState(telegramToggleDP)) {
            createState(telegramToggleDP, false, { type: 'boolean', name: 'Telegram aktivieren', read: true, write: true });
        }
        
        function getTokenAndContainers() {
            const loginCmd = `curl -s -H "Content-Type: application/json" -X POST -d '{"Username":"${username}","Password":"${password}"}' ${portainerHost}/api/auth`;
            exec(loginCmd, (error, stdout) => {
                if (error) return log(`❌ Token-Fehler: ${error}`, 'error');
                try {
                    const token = JSON.parse(stdout).jwt;
                    if (!token) return log('❌ Kein Token erhalten', 'error');
                    getContainerList(token);
                } catch (e) {
                    log(`❌ Token-Parsing-Fehler: ${e.message}`, 'error');
                }
            });
        }
        
        function getContainerList(token) {
            const url = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/json?all=1`;
            const cmd = `curl -s -H "Authorization: Bearer ${token}" "${url}"`;
            exec(cmd, (error, stdout) => {
                if (error) return log(`❌ Fehler beim Containerabruf: ${error}`, 'error');
        
                try {
                    const containers = JSON.parse(stdout);
                    const containerList = [];
        
                    containers.forEach(container => {
                        const id = container.Id;
                        const name = container.Names[0].replace(/^\//, '');
                        const dp = `${datapointBase}${name}`;
        
                        const image = container.Image;
                        const createdDate = new Date(container.Created * 1000);
                        const createdDaysAgo = Math.floor((Date.now() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
                        const created = `${createdDate.toLocaleString('de-DE', { hour12: false })} (${createdDaysAgo} Tage alt)`;
                        const ports = (container.Ports || []).map(p => `${p.IP || '0.0.0.0'}:${p.PublicPort}->${p.PrivatePort}/${p.Type}`).join(', ');
                        const ip = container.NetworkSettings?.Networks?.bridge?.IPAddress || 'n/a';
        
                        createState(`${dp}.image`, image, true);
                        setState(`${dp}.image`, image, true);
                        createState(`${dp}.created`, created, true);
                        setState(`${dp}.created`, created, true);
                        createState(`${dp}.ports`, ports, true);
                        setState(`${dp}.ports`, ports, true);
                        createState(`${dp}.ip`, ip, true);
                        setState(`${dp}.ip`, ip, true);
                        createState(`${dp}.control`, false, { type: 'boolean', role: 'button', write: true, read: false });
        
                        const detailUrl = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/${id}/json`;
                        const detailCmd = `curl -s -H "Authorization: Bearer ${token}" "${detailUrl}"`;
                        exec(detailCmd, (err, detailOut) => {
                            if (err) return log(`❌ Fehler bei Details für ${name}: ${err}`, 'warn');
                            try {
                                const detail = JSON.parse(detailOut);
                                const isRunning = detail?.State?.Running === true;
                                const state = isRunning ? 'running' : 'exited';
                                const startedAt = new Date(isRunning ? detail.State.StartedAt : 0);
                                const now = new Date();
                                const uptimeMs = isRunning ? now - startedAt : 0;
                                const uptimeText = formatUptime(uptimeMs);
        
                                const stateDP = `${dp}.state`;
                                getStateAsync(stateDP).then(oldState => {
                                    if (oldState && oldState.val !== state) {
                                        const msg = `🔔 Container \`${name}\` Status: \`${oldState.val}\` → \`${state}\``;
                                        sendDiscordMessage(msg);
                                        sendTelegramMessage(msg);
                                    }
                                });
        
                                createState(stateDP, state, true);
                                setState(stateDP, state, true);
                                createState(`${dp}.uptimeText`, uptimeText, true);
                                setState(`${dp}.uptimeText`, uptimeText, true);
        
                                const statsCmd = `curl -s -H "Authorization: Bearer ${token}" "${portainerHost}/api/endpoints/${endpointId}/docker/containers/${id}/stats?stream=false"`;
                                exec(statsCmd, (e, statsOut) => {
                                    if (e) return;
                                    try {
                                        const stats = JSON.parse(statsOut);
                                        const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
                                        const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
                                        const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * stats.cpu_stats.online_cpus * 100 : 0;
                                        const memMB = Math.round((stats.memory_stats.usage || 0) / 1024 / 1024);
        
                                        createState(`${dp}.cpu`, Math.round(cpuPercent * 10) / 10, true);
                                        setState(`${dp}.cpu`, Math.round(cpuPercent * 10) / 10, true);
                                        createState(`${dp}.mem`, memMB, true);
                                        setState(`${dp}.mem`, memMB, true);
        
                                        const cpuSafe = Math.round(cpuPercent * 10) / 10;
                                        const memSafe = memMB;
                                        const restartedRecently = isRunning && (uptimeMs < 2 * 60 * 1000);
                                        const restartAt = isRunning ? startedAt.toLocaleString('de-DE', { hour12: false }) : '-';
        containerList.push({ name, state, uptime: uptimeText, cpu: cpuSafe, mem: memSafe, image, ports, created, ip, restartedRecently, restartAt });
        
                                        setState(statusBoardDP, buildHTML(containerList), true);
                                        setState(mobileStatusDP, buildMobileHTML(containerList), true);
                                    } catch (e) {
                                        log(`⚠️ Stats-Parsing-Fehler ${name}: ${e.message}`, 'warn');
                                    }
                                });
        
                                
        
                            } catch (e) {
                                log(`❌ Detail-Parsing-Fehler ${name}: ${e.message}`, 'warn');
                            }
                        });
                    });
                } catch (err) {
                    log(`❌ Containerdaten-Fehler: ${err.message}`, 'error');
                }
            });
        }
        
        function formatUptime(ms) {
            const totalSeconds = Math.floor(ms / 1000);
            const days = Math.floor(totalSeconds / 86400);
            const hours = Math.floor((totalSeconds % 86400) / 3600);
            const minutes = Math.floor((totalSeconds % 3600) / 60);
            let str = '';
            if (days > 0) str += `${days}d `;
            if (days > 0 || hours > 0) str += `${hours}h `;
            str += `${minutes}min`;
            return str.trim();
        }
        
        on(new RegExp(`${datapointBase}(.*)\.control`), function (obj) {
            if (obj.state.val !== true) return;
            const name = obj.id.split('.')[4];
            const loginCmd = `curl -s -H "Content-Type: application/json" -X POST -d '{"Username":"${username}","Password":"${password}"}' ${portainerHost}/api/auth`;
            exec(loginCmd, (error, stdout) => {
                if (error) return log(`❌ Loginfehler: ${error}`, 'error');
                try {
                    const token = JSON.parse(stdout).jwt;
                    const listCmd = `curl -s -H "Authorization: Bearer ${token}" "${portainerHost}/api/endpoints/${endpointId}/docker/containers/json?all=1"`;
                    exec(listCmd, (err, out) => {
                        const list = JSON.parse(out);
                        const container = list.find(c => c.Names[0].replace(/^\//, '') === name);
                        if (!container) return log(`⚠️ Container ${name} nicht gefunden`, 'warn');
                        const action = container.State === 'running' ? 'stop' : 'start';
                        const actionUrl = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/${container.Id}/${action}`;
                        const actionCmd = `curl -s -H "Authorization: Bearer ${token}" -X POST "${actionUrl}"`;
                        exec(actionCmd, () => {
                            log(`✅ Container ${name} ${action} ausgeführt`);
                            setTimeout(() => getTokenAndContainers(), 3000);
                            setTimeout(() => setState(obj.id, false, true), 500);
                        });
                    });
                } catch (e) {
                    log(`❌ Steuerungsfehler: ${e.message}`, 'error');
                }
            });
        });
        
        function sendDiscordMessage(message) {
            const discordEnabled = getState(discordToggleDP)?.val === true;
            if (discordEnabled && existsState(discordDP)) {
                setState(discordDP, message);
                log(`📤 Discord: ${message}`);
            } else {
                log(`ℹ️ Discord deaktiviert oder nicht verfügbar`, 'info');
            }
        }
        
        function sendTelegramMessage(message) {
            const telegramEnabled = getState(telegramToggleDP)?.val === true;
            if (telegramEnabled) {
                sendTo(telegramInstance, 'send', { text: message });
                log(`📤 Telegram: ${message}`);
            } else {
                log(`ℹ️ Telegram deaktiviert`, 'info');
            }
        }
        
        function buildHTML(list) {
            list.sort((a, b) => a.name.localeCompare(b.name));
            let html = `<b style="color:#ffffff;">🧾 Docker Statusboard</b><br>
            <label for="runningFilter" style="color:#ffffff;">🚦 Nur laufende anzeigen</label>
            <input type="checkbox" id="runningFilter" onchange="filterRunning()" style="margin-bottom:10px;">
            <label for="themeToggle" style="color:#ffffff;">🌗 Theme</label>
            <input type="checkbox" id="themeToggle" checked onchange="toggleTheme()" style="margin-bottom: 10px;"><br>
            <table id="dockerStatus" border="1" cellspacing="0" cellpadding="6"
                style="font-family:Arial; font-size:13px; border-collapse:collapse;
                       text-align:left; background:#1e1e1e; color:#ffffff; border-color:#444; width:100%;">
                <thead>
                <tr style="background:#333;">
                    <th onclick="sortTable(0)">Name</th>
                    <th onclick="sortTable(1)">CPU</th>
                    <th onclick="sortTable(2)">RAM</th>
                    <th onclick="sortTable(3)">Status</th>
                    <th onclick="sortTable(4)">Uptime</th>
                    <th onclick="sortTable(5)">Restarted</th>
                    <th onclick="sortTable(6)">Restart-Zeit</th>
                    <th onclick="sortTable(7)">Image</th>
                    <th onclick="sortTable(8)">Erstellt</th>
                    <th onclick="sortTable(9)">Ports</th>
                </tr>
                </thead><tbody>`;
        
            for (let c of list) {
                html += `<tr style="background:${c.state === 'running' ? '#2d572c' : '#571b1b'};" title="IP: ${c.ip}">
                    <td>${c.name}</td>
                    <td style="color:${c.cpu > 50 ? '#ff5555' : '#cccccc'}">${c.cpu}</td>
                    <td style="color:${c.mem > 500 ? '#ff8800' : '#cccccc'}">${c.mem}</td>
                    <td>${c.state === 'running' ? '✅' : '❌'} ${c.state}</td>
                    <td>${c.uptime}</td>
                    <td style="color: ${c.restartedRecently ? '#ffaa00' : '#cccccc'};">${c.restartedRecently ? 'ja' : 'nein'}</td>
                    <td>${c.restartAt}</td>
                    <td>${c.image}</td>
                    <td>${c.created}</td>
                    <td>${c.ports}</td>
                </tr>`;
            }
        
            html += `</tbody></table>
            <script>
                function filterRunning() {
                    var table = document.getElementById("dockerStatus");
                    var rows = table.getElementsByTagName("tr");
                    var onlyRunning = document.getElementById("runningFilter").checked;
                    for (var i = 1; i < rows.length; i++) {
                        var stateCell = rows[i].getElementsByTagName("td")[3];
                        if (!stateCell) continue;
                        var isRunning = stateCell.textContent.includes("running");
                        rows[i].style.display = (onlyRunning && !isRunning) ? "none" : "";
                    }
                }
                function sortTable(n) {
                    var table = document.getElementById("dockerStatus");
                    var switching = true;
                    var dir = "asc";
                    var switchcount = 0;
                    while (switching) {
                        switching = false;
                        var rows = table.rows;
                        for (var i = 1; i < rows.length - 1; i++) {
                            var shouldSwitch = false;
                            var x = rows[i].getElementsByTagName("TD")[n];
                            var y = rows[i + 1].getElementsByTagName("TD")[n];
                            var xContent = x.textContent || x.innerText;
                            var yContent = y.textContent || y.innerText;
                            var xNum = parseFloat(xContent);
                            var yNum = parseFloat(yContent);
                            var isNumber = !isNaN(xNum) && !isNaN(yNum);
                            if (isNumber) {
                                if ((dir === "asc" && xNum > yNum) || (dir === "desc" && xNum < yNum)) {
                                    shouldSwitch = true;
                                    break;
                                }
                            } else {
                                if ((dir === "asc" && xContent.toLowerCase() > yContent.toLowerCase()) ||
                                    (dir === "desc" && xContent.toLowerCase() < yContent.toLowerCase())) {
                                    shouldSwitch = true;
                                    break;
                                }
                            }
                        }
                        if (shouldSwitch) {
                            rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
                            switching = true;
                            switchcount++;
                        } else {
                            if (switchcount === 0 && dir === "asc") {
                                dir = "desc";
                                switching = true;
                            }
                        }
                    }
                }
            function toggleTheme() {
                    var table = document.getElementById("dockerStatus");
                    var isDark = document.getElementById("themeToggle").checked;
                    table.style.background = isDark ? "#1e1e1e" : "#ffffff";
                    table.style.color = isDark ? "#ffffff" : "#000000";
                    var th = table.getElementsByTagName("th");
                    for (var i = 0; i < th.length; i++) {
                        th[i].style.background = isDark ? "#333" : "#dddddd";
                    }
                    localStorage.setItem("themeDark", isDark);
                }
                window.addEventListener("load", () => {
                    document.getElementById("themeToggle").checked = localStorage.getItem("themeDark") === "true";
                    toggleTheme();
                });
                }
            </script>`;
            return html;
        }
        
        // Start und Zyklus
        getTokenAndContainers();
        schedule(`*/${updateIntervalMinutes} * * * *`, getTokenAndContainers);
        
        function buildMobileHTML(list) {
            list.sort((a, b) => a.name.localeCompare(b.name));
            let html = `<div style="font-family:Arial; font-size:14px;"><b>📱 Docker Übersicht</b><br><br>`;
        
            const columnWidth = mobileLayoutColumns === 2 ? '48%' : '100%';
            const wrapperStyle = mobileLayoutColumns === 2 ? 'display:flex; flex-wrap:wrap; justify-content:space-between;' : '';
        
            html += `<div style="${wrapperStyle}">`;
        
            for (let c of list) {
                const color = c.state === 'running' ? '#2d572c' : '#571b1b';
                const cpu = parseFloat(c.cpu);
                const mem = parseFloat(c.mem);
                const statusText = mobileUseSymbol ? (c.state === 'running' ? '✅' : '❌') : c.state;
                let metricsLine = '';
                if (mobileShowCPU) metricsLine += ` CPU: ${cpu}%`;
                if (mobileShowRAM) metricsLine += ` RAM: ${mem} MB`;
        
                let card = '';
                if (mobileTwoLineLayout) {
                    card = `<div style="margin-bottom:12px; padding:8px; border-radius:8px; background:${color}; width:${columnWidth}; box-sizing:border-box;">
                        <b>${c.name}</b><br>
                        <small>${statusText}${metricsLine}</small><br>
                        <small>Uptime: ${c.uptime}</small>
                    </div>`;
                } else {
                    card = `<div style="margin-bottom:12px; padding:8px; border-radius:8px; background:${color}; width:${columnWidth}; box-sizing:border-box;">
                        <b>${c.name}</b><br>
                        <small>Status: ${statusText}${metricsLine}, Uptime: ${c.uptime}</small>
                    </div>`;
                }
                html += card;
            }
        
            html += `</div></div>`;
            return html;
        }
        
        
        

        Edit2:
        Screenshot von den Objects:
        Screenshot 2025-08-01 at 14.38.20.png

        Edit3:
        Script um eine html Tabelle aus den States zu erzeugen, Datenpunkte und Container bitte anpassen:
        wurde alles in ein Script gepackt, ist einfacher.

        Screenshot: Tabelle
        Screenshot 2025-08-01 at 16.15.07.png

        Screenshot: Mobile Ansicht, 2 Spaltig (konfigurierbar)
        Screenshot 2025-08-04 at 20.53.06.png

        David G.D Offline
        David G.D Offline
        David G.
        schrieb am zuletzt editiert von
        #3

        @ilovegym

        Warum auch immer klappt blockly grad bei mir nicht und darf keine Variablen anlegen.....

        Du kannst aus der Blockly eine Liste erstellen und dann mit einer Schleife durcharbeiten.

        Zeigt eure Lovelace-Visualisierung klick
        (Auch ideal um sich Anregungen zu holen)

        Meine Tabellen für eure Visualisierung klick

        1 Antwort Letzte Antwort
        1
        • ilovegymI Online
          ilovegymI Online
          ilovegym
          schrieb am zuletzt editiert von ilovegym
          #4

          @david-g @Codierknecht

          hab mal meinen "Sheldon" :) gefragt (Chatgpt), und das kam zum schluss raus, habs noch n bisserl verfeinert, da die ersten Ansaetze nix waren..

          Das Script fragt direkt die Portainer-Api ab, einen Api-Key braucht man nicht und legt die Datenpunkte fuer Status, Ram, CPU und uptime im gewuenschten Ordner an.
          Man kann den/die Container starten/stoppen.

          Script in den ersten Beitrag verschoben, dieser wird immer aktualisiert.

          David G.D 1 Antwort Letzte Antwort
          0
          • ilovegymI ilovegym

            @david-g @Codierknecht

            hab mal meinen "Sheldon" :) gefragt (Chatgpt), und das kam zum schluss raus, habs noch n bisserl verfeinert, da die ersten Ansaetze nix waren..

            Das Script fragt direkt die Portainer-Api ab, einen Api-Key braucht man nicht und legt die Datenpunkte fuer Status, Ram, CPU und uptime im gewuenschten Ordner an.
            Man kann den/die Container starten/stoppen.

            Script in den ersten Beitrag verschoben, dieser wird immer aktualisiert.

            David G.D Offline
            David G.D Offline
            David G.
            schrieb am zuletzt editiert von David G.
            #5

            @ilovegym

            Hat sich erledigt.
            Auch meine Nachricht.

            Zeigt eure Lovelace-Visualisierung klick
            (Auch ideal um sich Anregungen zu holen)

            Meine Tabellen für eure Visualisierung klick

            ilovegymI 1 Antwort Letzte Antwort
            0
            • David G.D David G.

              @ilovegym

              Hat sich erledigt.
              Auch meine Nachricht.

              ilovegymI Online
              ilovegymI Online
              ilovegym
              schrieb am zuletzt editiert von
              #6

              @david-g

              alles gut, bin gerade am erweitern / optimieren des Scripts..

              David G.D 1 Antwort Letzte Antwort
              0
              • ilovegymI ilovegym

                @david-g

                alles gut, bin gerade am erweitern / optimieren des Scripts..

                David G.D Offline
                David G.D Offline
                David G.
                schrieb am zuletzt editiert von
                #7

                @ilovegym

                Bei mir möchte es auch nicht ganz.

                avascript.0	14:28:18.301	info	
                Stopping script script.js.Eigene_Scripte.Portainer
                javascript.0	14:28:18.359	info	
                Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                javascript.0	14:28:18.382	info	
                registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                javascript.0	14:28:18.470	error	
                ❌ Fehler beim Parsen der Containerdaten: containers.forEach is not a function
                

                Zeigt eure Lovelace-Visualisierung klick
                (Auch ideal um sich Anregungen zu holen)

                Meine Tabellen für eure Visualisierung klick

                ilovegymI 1 Antwort Letzte Antwort
                0
                • David G.D David G.

                  @ilovegym

                  Bei mir möchte es auch nicht ganz.

                  avascript.0	14:28:18.301	info	
                  Stopping script script.js.Eigene_Scripte.Portainer
                  javascript.0	14:28:18.359	info	
                  Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                  javascript.0	14:28:18.382	info	
                  registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                  javascript.0	14:28:18.470	error	
                  ❌ Fehler beim Parsen der Containerdaten: containers.forEach is not a function
                  
                  ilovegymI Online
                  ilovegymI Online
                  ilovegym
                  schrieb am zuletzt editiert von
                  #8

                  @david-g

                  endpointid richtig? muss nicht 1 sein,bei ist es 2

                  David G.D 1 Antwort Letzte Antwort
                  0
                  • ilovegymI ilovegym

                    @david-g

                    endpointid richtig? muss nicht 1 sein,bei ist es 2

                    David G.D Offline
                    David G.D Offline
                    David G.
                    schrieb am zuletzt editiert von
                    #9

                    @ilovegym
                    Hab 2 getestet. Dann kommt keinen token erhalten. Dann wieder 1. Kommt jetzt auch keinen token erhalten.

                    Zeigt eure Lovelace-Visualisierung klick
                    (Auch ideal um sich Anregungen zu holen)

                    Meine Tabellen für eure Visualisierung klick

                    ilovegymI 1 Antwort Letzte Antwort
                    0
                    • David G.D David G.

                      @ilovegym
                      Hab 2 getestet. Dann kommt keinen token erhalten. Dann wieder 1. Kommt jetzt auch keinen token erhalten.

                      ilovegymI Online
                      ilovegymI Online
                      ilovegym
                      schrieb am zuletzt editiert von
                      #10

                      @david-g

                      hmm vielleicht Portainer wegen zuvielen falschen Zugriffen geblockt, war bei mir auch am Anfang, als das Script noch fehler hatte.. einfach Portainer neu starten...

                      David G.D 1 Antwort Letzte Antwort
                      0
                      • ilovegymI ilovegym

                        @david-g

                        hmm vielleicht Portainer wegen zuvielen falschen Zugriffen geblockt, war bei mir auch am Anfang, als das Script noch fehler hatte.. einfach Portainer neu starten...

                        David G.D Offline
                        David G.D Offline
                        David G.
                        schrieb am zuletzt editiert von David G.
                        #11

                        @ilovegym

                        Die ganzen warns waren mit id 2, der Fehler beim Pharsen mit 1.

                        1.8.2025, 14:43:04.923	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 14:43:05.015	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 14:43:05.037	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 14:43:05.045	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 14:43:54.160	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 14:43:54.208	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 14:43:54.230	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 14:43:54.239	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 14:43:58.696	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 14:44:01.907	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 14:44:01.913	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 14:44:01.918	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 14:44:15.888	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 14:44:15.978	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 14:44:15.986	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 14:44:15.994	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 14:45:00.067	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 14:46:35.734	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 14:46:35.824	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 14:46:35.844	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 14:46:35.851	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 14:52:03.359	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 14:52:03.450	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 14:52:03.474	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 14:52:03.481	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 14:52:16.268	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 14:52:16.358	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 14:52:16.377	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 14:52:16.395	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 14:52:29.449	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 14:52:29.537	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 14:52:29.561	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 14:52:29.570	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Kein Token erhalten
                        1.8.2025, 15:01:54.921	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 15:01:55.010	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 15:01:55.023	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 15:01:55.205	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:55.205	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:55.206	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:55.206	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:55.206	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:55.208	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:55.208	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:55.208	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:55.208	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:55.209	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:55.209	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:55.209	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:56.195	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:56.196	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:57.205	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:57.206	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:57.211	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:57.213	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:57.218	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:57.224	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:57.231	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:57.233	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:57.235	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:57.236	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:57.236	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:57.237	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:57.240	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:57.240	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:01:57.241	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:96:25
                        1.8.2025, 15:01:57.241	[warn ]: javascript.0 (1615949)     at script.js.Eigene_Scripte.Portainer:99:25
                        1.8.2025, 15:02:14.020	[info ]: javascript.0 (1615949) Stopping script script.js.Eigene_Scripte.Portainer
                        1.8.2025, 15:02:14.111	[info ]: javascript.0 (1615949) Start JavaScript script.js.Eigene_Scripte.Portainer (Javascript/js)
                        1.8.2025, 15:02:14.130	[info ]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                        1.8.2025, 15:02:14.221	[error]: javascript.0 (1615949) script.js.Eigene_Scripte.Portainer: ❌ Fehler beim Parsen der Containerdaten: containers.forEach is not a function
                        

                        Portainer 2.27.9

                        Edit
                        Evtl kann man den Endpoint automatisch ausfüllen, wenn man diesen leer lässt.

                        curl -H "Authorization: Bearer <JWT>" \
                             http://10.68.xx.zzz:9000/api/endpoints
                        

                        und dann den ersten falls es mehrere gibt.

                        Zeigt eure Lovelace-Visualisierung klick
                        (Auch ideal um sich Anregungen zu holen)

                        Meine Tabellen für eure Visualisierung klick

                        1 Antwort Letzte Antwort
                        0
                        • ilovegymI ilovegym

                          Edit: Header, Text, Scripte :). vielen Dank an alle die mir hier geholfen haben!

                          Hallo,
                          wie bekomme ich die Daten aus Portainer in den iobroker?

                          Edit:
                          Hier das, was chatgpt nach einigen Versuchen mir ausgegeben hat, das Javascript fragt die Portainer-Api in regelmaessigen Abstaenden ab, man kann Docker Container starten/stoppen, bekommt bei Aenderungen eine Msg per Discord, und es werden Cpu, Ram, Uptime, Image, IP, Port angezeigt:

                          // 📦 Portainer Statusboard Version 3.3
                          // by Ilovegym
                          // 📜 Änderungsprotokoll
                          // - Version 3.3: Mobile Ansicht – CPU/RAM ein-/ausblendbar
                          // - Version 3.2: Mobile Ansicht mit Symbolstatus & 2-zeiligem Layout konfigurierbar
                          // - Version 3.1: Mobiler Dashboard-Datenpunkt konfigurierbar
                          // - Version 3.0: Mobiles Dashboard in Datenpunkt StatusMobile
                          // - Version 2.7: Persistenter Theme-Switch
                          // - Version 2.6: Persistente Filter und zusätzliche Filterfelder (CPU, RAM, Alter)
                          // - Version 2.5: Filter für laufende Container eingebaut
                          // - Version 2.4: "Erstellt" zeigt nun Alter in Tagen an
                          // - Version 2.3: Alphabetische Standard-Sortierung
                          // - Version 2.2: Warn-Icons, Farb-Balken für CPU/RAM, letzter Restart-Zeitpunkt
                          // - Version 2.1: CPU und RAM korrekt aus Stats übernommen
                          // - Version 2.0: Hinzugefügt: Restarted-Spalte, Tooltip mit IP, Theme-Switch, Sortierung
                          // - Version 1.x: Basisfunktion: Containerdatenpunkte, Discord/Telegram Benachrichtigungen, Statusboard
                          const exec = require('child_process').exec;
                          
                          // 🔧 KONFIGURATION
                          const mobileLayoutColumns = 1; // 1 = eine Spalte, 2 = zwei Spalten in mobiler Ansicht
                          
                          const mobileColumnOrder = [
                              'name', 'state', 'cpu', 'mem', 'uptime'
                          ]; // Spaltenreihenfolge in der mobilen Ansicht
                          
                          const tableColumnOrder = [
                              'name', 'cpu', 'mem', 'state', 'uptime', 'restartedRecently', 'restartAt', 'image', 'created', 'ports'
                          ]; // Reihenfolge der Spalten im Statusboard
                          
                          const username = 'admin';
                          const password = 'supergeheimespassword!';
                          const portainerHost = 'http://10.10.2.10:9000';
                          const endpointId = 2;  //default 1
                          const datapointBase = '0_userdata.0.Portainer.Containers.';
                          const statusBoardDP = '0_userdata.0.Portainer.Containers.Statusboard';
                          const updateIntervalMinutes = 3;
                          const mobileStatusDP = '0_userdata.0.Portainer.Containers.StatusMobile';
                          const mobileUseSymbol = true;       // true = ✅/❌, false = 'running'
                          const mobileShowCPU = true;         // true = CPU in mobiler Ansicht anzeigen
                          const mobileShowRAM = true;         // true = RAM in mobiler Ansicht anzeigen
                          const mobileTwoLineLayout = true;   // true = Name/Status oben, Details unten
                          const discordDP = 'discord.0.xxxx.send'; //bitte anpassen
                          
                          const discordToggleDP = '0_userdata.0.Portainer.Notify.Discord';
                          const telegramToggleDP = '0_userdata.0.Portainer.Notify.Telegram';
                          const telegramInstance = 'telegram.0';
                          
                          // Initiale Schalter
                          if (!existsState(discordToggleDP)) {
                              createState(discordToggleDP, true, { type: 'boolean', name: 'Discord aktivieren', read: true, write: true });
                          }
                          if (!existsState(telegramToggleDP)) {
                              createState(telegramToggleDP, false, { type: 'boolean', name: 'Telegram aktivieren', read: true, write: true });
                          }
                          
                          function getTokenAndContainers() {
                              const loginCmd = `curl -s -H "Content-Type: application/json" -X POST -d '{"Username":"${username}","Password":"${password}"}' ${portainerHost}/api/auth`;
                              exec(loginCmd, (error, stdout) => {
                                  if (error) return log(`❌ Token-Fehler: ${error}`, 'error');
                                  try {
                                      const token = JSON.parse(stdout).jwt;
                                      if (!token) return log('❌ Kein Token erhalten', 'error');
                                      getContainerList(token);
                                  } catch (e) {
                                      log(`❌ Token-Parsing-Fehler: ${e.message}`, 'error');
                                  }
                              });
                          }
                          
                          function getContainerList(token) {
                              const url = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/json?all=1`;
                              const cmd = `curl -s -H "Authorization: Bearer ${token}" "${url}"`;
                              exec(cmd, (error, stdout) => {
                                  if (error) return log(`❌ Fehler beim Containerabruf: ${error}`, 'error');
                          
                                  try {
                                      const containers = JSON.parse(stdout);
                                      const containerList = [];
                          
                                      containers.forEach(container => {
                                          const id = container.Id;
                                          const name = container.Names[0].replace(/^\//, '');
                                          const dp = `${datapointBase}${name}`;
                          
                                          const image = container.Image;
                                          const createdDate = new Date(container.Created * 1000);
                                          const createdDaysAgo = Math.floor((Date.now() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
                                          const created = `${createdDate.toLocaleString('de-DE', { hour12: false })} (${createdDaysAgo} Tage alt)`;
                                          const ports = (container.Ports || []).map(p => `${p.IP || '0.0.0.0'}:${p.PublicPort}->${p.PrivatePort}/${p.Type}`).join(', ');
                                          const ip = container.NetworkSettings?.Networks?.bridge?.IPAddress || 'n/a';
                          
                                          createState(`${dp}.image`, image, true);
                                          setState(`${dp}.image`, image, true);
                                          createState(`${dp}.created`, created, true);
                                          setState(`${dp}.created`, created, true);
                                          createState(`${dp}.ports`, ports, true);
                                          setState(`${dp}.ports`, ports, true);
                                          createState(`${dp}.ip`, ip, true);
                                          setState(`${dp}.ip`, ip, true);
                                          createState(`${dp}.control`, false, { type: 'boolean', role: 'button', write: true, read: false });
                          
                                          const detailUrl = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/${id}/json`;
                                          const detailCmd = `curl -s -H "Authorization: Bearer ${token}" "${detailUrl}"`;
                                          exec(detailCmd, (err, detailOut) => {
                                              if (err) return log(`❌ Fehler bei Details für ${name}: ${err}`, 'warn');
                                              try {
                                                  const detail = JSON.parse(detailOut);
                                                  const isRunning = detail?.State?.Running === true;
                                                  const state = isRunning ? 'running' : 'exited';
                                                  const startedAt = new Date(isRunning ? detail.State.StartedAt : 0);
                                                  const now = new Date();
                                                  const uptimeMs = isRunning ? now - startedAt : 0;
                                                  const uptimeText = formatUptime(uptimeMs);
                          
                                                  const stateDP = `${dp}.state`;
                                                  getStateAsync(stateDP).then(oldState => {
                                                      if (oldState && oldState.val !== state) {
                                                          const msg = `🔔 Container \`${name}\` Status: \`${oldState.val}\` → \`${state}\``;
                                                          sendDiscordMessage(msg);
                                                          sendTelegramMessage(msg);
                                                      }
                                                  });
                          
                                                  createState(stateDP, state, true);
                                                  setState(stateDP, state, true);
                                                  createState(`${dp}.uptimeText`, uptimeText, true);
                                                  setState(`${dp}.uptimeText`, uptimeText, true);
                          
                                                  const statsCmd = `curl -s -H "Authorization: Bearer ${token}" "${portainerHost}/api/endpoints/${endpointId}/docker/containers/${id}/stats?stream=false"`;
                                                  exec(statsCmd, (e, statsOut) => {
                                                      if (e) return;
                                                      try {
                                                          const stats = JSON.parse(statsOut);
                                                          const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
                                                          const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
                                                          const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * stats.cpu_stats.online_cpus * 100 : 0;
                                                          const memMB = Math.round((stats.memory_stats.usage || 0) / 1024 / 1024);
                          
                                                          createState(`${dp}.cpu`, Math.round(cpuPercent * 10) / 10, true);
                                                          setState(`${dp}.cpu`, Math.round(cpuPercent * 10) / 10, true);
                                                          createState(`${dp}.mem`, memMB, true);
                                                          setState(`${dp}.mem`, memMB, true);
                          
                                                          const cpuSafe = Math.round(cpuPercent * 10) / 10;
                                                          const memSafe = memMB;
                                                          const restartedRecently = isRunning && (uptimeMs < 2 * 60 * 1000);
                                                          const restartAt = isRunning ? startedAt.toLocaleString('de-DE', { hour12: false }) : '-';
                          containerList.push({ name, state, uptime: uptimeText, cpu: cpuSafe, mem: memSafe, image, ports, created, ip, restartedRecently, restartAt });
                          
                                                          setState(statusBoardDP, buildHTML(containerList), true);
                                                          setState(mobileStatusDP, buildMobileHTML(containerList), true);
                                                      } catch (e) {
                                                          log(`⚠️ Stats-Parsing-Fehler ${name}: ${e.message}`, 'warn');
                                                      }
                                                  });
                          
                                                  
                          
                                              } catch (e) {
                                                  log(`❌ Detail-Parsing-Fehler ${name}: ${e.message}`, 'warn');
                                              }
                                          });
                                      });
                                  } catch (err) {
                                      log(`❌ Containerdaten-Fehler: ${err.message}`, 'error');
                                  }
                              });
                          }
                          
                          function formatUptime(ms) {
                              const totalSeconds = Math.floor(ms / 1000);
                              const days = Math.floor(totalSeconds / 86400);
                              const hours = Math.floor((totalSeconds % 86400) / 3600);
                              const minutes = Math.floor((totalSeconds % 3600) / 60);
                              let str = '';
                              if (days > 0) str += `${days}d `;
                              if (days > 0 || hours > 0) str += `${hours}h `;
                              str += `${minutes}min`;
                              return str.trim();
                          }
                          
                          on(new RegExp(`${datapointBase}(.*)\.control`), function (obj) {
                              if (obj.state.val !== true) return;
                              const name = obj.id.split('.')[4];
                              const loginCmd = `curl -s -H "Content-Type: application/json" -X POST -d '{"Username":"${username}","Password":"${password}"}' ${portainerHost}/api/auth`;
                              exec(loginCmd, (error, stdout) => {
                                  if (error) return log(`❌ Loginfehler: ${error}`, 'error');
                                  try {
                                      const token = JSON.parse(stdout).jwt;
                                      const listCmd = `curl -s -H "Authorization: Bearer ${token}" "${portainerHost}/api/endpoints/${endpointId}/docker/containers/json?all=1"`;
                                      exec(listCmd, (err, out) => {
                                          const list = JSON.parse(out);
                                          const container = list.find(c => c.Names[0].replace(/^\//, '') === name);
                                          if (!container) return log(`⚠️ Container ${name} nicht gefunden`, 'warn');
                                          const action = container.State === 'running' ? 'stop' : 'start';
                                          const actionUrl = `${portainerHost}/api/endpoints/${endpointId}/docker/containers/${container.Id}/${action}`;
                                          const actionCmd = `curl -s -H "Authorization: Bearer ${token}" -X POST "${actionUrl}"`;
                                          exec(actionCmd, () => {
                                              log(`✅ Container ${name} ${action} ausgeführt`);
                                              setTimeout(() => getTokenAndContainers(), 3000);
                                              setTimeout(() => setState(obj.id, false, true), 500);
                                          });
                                      });
                                  } catch (e) {
                                      log(`❌ Steuerungsfehler: ${e.message}`, 'error');
                                  }
                              });
                          });
                          
                          function sendDiscordMessage(message) {
                              const discordEnabled = getState(discordToggleDP)?.val === true;
                              if (discordEnabled && existsState(discordDP)) {
                                  setState(discordDP, message);
                                  log(`📤 Discord: ${message}`);
                              } else {
                                  log(`ℹ️ Discord deaktiviert oder nicht verfügbar`, 'info');
                              }
                          }
                          
                          function sendTelegramMessage(message) {
                              const telegramEnabled = getState(telegramToggleDP)?.val === true;
                              if (telegramEnabled) {
                                  sendTo(telegramInstance, 'send', { text: message });
                                  log(`📤 Telegram: ${message}`);
                              } else {
                                  log(`ℹ️ Telegram deaktiviert`, 'info');
                              }
                          }
                          
                          function buildHTML(list) {
                              list.sort((a, b) => a.name.localeCompare(b.name));
                              let html = `<b style="color:#ffffff;">🧾 Docker Statusboard</b><br>
                              <label for="runningFilter" style="color:#ffffff;">🚦 Nur laufende anzeigen</label>
                              <input type="checkbox" id="runningFilter" onchange="filterRunning()" style="margin-bottom:10px;">
                              <label for="themeToggle" style="color:#ffffff;">🌗 Theme</label>
                              <input type="checkbox" id="themeToggle" checked onchange="toggleTheme()" style="margin-bottom: 10px;"><br>
                              <table id="dockerStatus" border="1" cellspacing="0" cellpadding="6"
                                  style="font-family:Arial; font-size:13px; border-collapse:collapse;
                                         text-align:left; background:#1e1e1e; color:#ffffff; border-color:#444; width:100%;">
                                  <thead>
                                  <tr style="background:#333;">
                                      <th onclick="sortTable(0)">Name</th>
                                      <th onclick="sortTable(1)">CPU</th>
                                      <th onclick="sortTable(2)">RAM</th>
                                      <th onclick="sortTable(3)">Status</th>
                                      <th onclick="sortTable(4)">Uptime</th>
                                      <th onclick="sortTable(5)">Restarted</th>
                                      <th onclick="sortTable(6)">Restart-Zeit</th>
                                      <th onclick="sortTable(7)">Image</th>
                                      <th onclick="sortTable(8)">Erstellt</th>
                                      <th onclick="sortTable(9)">Ports</th>
                                  </tr>
                                  </thead><tbody>`;
                          
                              for (let c of list) {
                                  html += `<tr style="background:${c.state === 'running' ? '#2d572c' : '#571b1b'};" title="IP: ${c.ip}">
                                      <td>${c.name}</td>
                                      <td style="color:${c.cpu > 50 ? '#ff5555' : '#cccccc'}">${c.cpu}</td>
                                      <td style="color:${c.mem > 500 ? '#ff8800' : '#cccccc'}">${c.mem}</td>
                                      <td>${c.state === 'running' ? '✅' : '❌'} ${c.state}</td>
                                      <td>${c.uptime}</td>
                                      <td style="color: ${c.restartedRecently ? '#ffaa00' : '#cccccc'};">${c.restartedRecently ? 'ja' : 'nein'}</td>
                                      <td>${c.restartAt}</td>
                                      <td>${c.image}</td>
                                      <td>${c.created}</td>
                                      <td>${c.ports}</td>
                                  </tr>`;
                              }
                          
                              html += `</tbody></table>
                              <script>
                                  function filterRunning() {
                                      var table = document.getElementById("dockerStatus");
                                      var rows = table.getElementsByTagName("tr");
                                      var onlyRunning = document.getElementById("runningFilter").checked;
                                      for (var i = 1; i < rows.length; i++) {
                                          var stateCell = rows[i].getElementsByTagName("td")[3];
                                          if (!stateCell) continue;
                                          var isRunning = stateCell.textContent.includes("running");
                                          rows[i].style.display = (onlyRunning && !isRunning) ? "none" : "";
                                      }
                                  }
                                  function sortTable(n) {
                                      var table = document.getElementById("dockerStatus");
                                      var switching = true;
                                      var dir = "asc";
                                      var switchcount = 0;
                                      while (switching) {
                                          switching = false;
                                          var rows = table.rows;
                                          for (var i = 1; i < rows.length - 1; i++) {
                                              var shouldSwitch = false;
                                              var x = rows[i].getElementsByTagName("TD")[n];
                                              var y = rows[i + 1].getElementsByTagName("TD")[n];
                                              var xContent = x.textContent || x.innerText;
                                              var yContent = y.textContent || y.innerText;
                                              var xNum = parseFloat(xContent);
                                              var yNum = parseFloat(yContent);
                                              var isNumber = !isNaN(xNum) && !isNaN(yNum);
                                              if (isNumber) {
                                                  if ((dir === "asc" && xNum > yNum) || (dir === "desc" && xNum < yNum)) {
                                                      shouldSwitch = true;
                                                      break;
                                                  }
                                              } else {
                                                  if ((dir === "asc" && xContent.toLowerCase() > yContent.toLowerCase()) ||
                                                      (dir === "desc" && xContent.toLowerCase() < yContent.toLowerCase())) {
                                                      shouldSwitch = true;
                                                      break;
                                                  }
                                              }
                                          }
                                          if (shouldSwitch) {
                                              rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
                                              switching = true;
                                              switchcount++;
                                          } else {
                                              if (switchcount === 0 && dir === "asc") {
                                                  dir = "desc";
                                                  switching = true;
                                              }
                                          }
                                      }
                                  }
                              function toggleTheme() {
                                      var table = document.getElementById("dockerStatus");
                                      var isDark = document.getElementById("themeToggle").checked;
                                      table.style.background = isDark ? "#1e1e1e" : "#ffffff";
                                      table.style.color = isDark ? "#ffffff" : "#000000";
                                      var th = table.getElementsByTagName("th");
                                      for (var i = 0; i < th.length; i++) {
                                          th[i].style.background = isDark ? "#333" : "#dddddd";
                                      }
                                      localStorage.setItem("themeDark", isDark);
                                  }
                                  window.addEventListener("load", () => {
                                      document.getElementById("themeToggle").checked = localStorage.getItem("themeDark") === "true";
                                      toggleTheme();
                                  });
                                  }
                              </script>`;
                              return html;
                          }
                          
                          // Start und Zyklus
                          getTokenAndContainers();
                          schedule(`*/${updateIntervalMinutes} * * * *`, getTokenAndContainers);
                          
                          function buildMobileHTML(list) {
                              list.sort((a, b) => a.name.localeCompare(b.name));
                              let html = `<div style="font-family:Arial; font-size:14px;"><b>📱 Docker Übersicht</b><br><br>`;
                          
                              const columnWidth = mobileLayoutColumns === 2 ? '48%' : '100%';
                              const wrapperStyle = mobileLayoutColumns === 2 ? 'display:flex; flex-wrap:wrap; justify-content:space-between;' : '';
                          
                              html += `<div style="${wrapperStyle}">`;
                          
                              for (let c of list) {
                                  const color = c.state === 'running' ? '#2d572c' : '#571b1b';
                                  const cpu = parseFloat(c.cpu);
                                  const mem = parseFloat(c.mem);
                                  const statusText = mobileUseSymbol ? (c.state === 'running' ? '✅' : '❌') : c.state;
                                  let metricsLine = '';
                                  if (mobileShowCPU) metricsLine += ` CPU: ${cpu}%`;
                                  if (mobileShowRAM) metricsLine += ` RAM: ${mem} MB`;
                          
                                  let card = '';
                                  if (mobileTwoLineLayout) {
                                      card = `<div style="margin-bottom:12px; padding:8px; border-radius:8px; background:${color}; width:${columnWidth}; box-sizing:border-box;">
                                          <b>${c.name}</b><br>
                                          <small>${statusText}${metricsLine}</small><br>
                                          <small>Uptime: ${c.uptime}</small>
                                      </div>`;
                                  } else {
                                      card = `<div style="margin-bottom:12px; padding:8px; border-radius:8px; background:${color}; width:${columnWidth}; box-sizing:border-box;">
                                          <b>${c.name}</b><br>
                                          <small>Status: ${statusText}${metricsLine}, Uptime: ${c.uptime}</small>
                                      </div>`;
                                  }
                                  html += card;
                              }
                          
                              html += `</div></div>`;
                              return html;
                          }
                          
                          
                          

                          Edit2:
                          Screenshot von den Objects:
                          Screenshot 2025-08-01 at 14.38.20.png

                          Edit3:
                          Script um eine html Tabelle aus den States zu erzeugen, Datenpunkte und Container bitte anpassen:
                          wurde alles in ein Script gepackt, ist einfacher.

                          Screenshot: Tabelle
                          Screenshot 2025-08-01 at 16.15.07.png

                          Screenshot: Mobile Ansicht, 2 Spaltig (konfigurierbar)
                          Screenshot 2025-08-04 at 20.53.06.png

                          David G.D Offline
                          David G.D Offline
                          David G.
                          schrieb am zuletzt editiert von David G.
                          #12

                          @ilovegym

                          Die neue Version läuft.
                          Top.

                          Was ich mir wünschen würde:

                          • Discord deaktivieren zu können (hab es mir einfach unten rausgelöscht).
                          • Ggf Telegram hinzufügen (natürlich auch deaktivierbar)
                          • Created auch als TS (Da schaue ich, wann das letzte Update war um Container zu finden die nicht mehr gepflegt werden)

                          EDIT
                          Soll control die Container updaten?
                          Falls ja klappt es bei mir nicht.

                          EDIT 2
                          Wenn ich control in einem Container drücke, startet meine JS Instanz neu.

                          Zeigt eure Lovelace-Visualisierung klick
                          (Auch ideal um sich Anregungen zu holen)

                          Meine Tabellen für eure Visualisierung klick

                          ilovegymI 2 Antworten Letzte Antwort
                          1
                          • David G.D David G.

                            @ilovegym

                            Die neue Version läuft.
                            Top.

                            Was ich mir wünschen würde:

                            • Discord deaktivieren zu können (hab es mir einfach unten rausgelöscht).
                            • Ggf Telegram hinzufügen (natürlich auch deaktivierbar)
                            • Created auch als TS (Da schaue ich, wann das letzte Update war um Container zu finden die nicht mehr gepflegt werden)

                            EDIT
                            Soll control die Container updaten?
                            Falls ja klappt es bei mir nicht.

                            EDIT 2
                            Wenn ich control in einem Container drücke, startet meine JS Instanz neu.

                            ilovegymI Online
                            ilovegymI Online
                            ilovegym
                            schrieb am zuletzt editiert von
                            #13

                            @david-g

                            Control soll den Container starten / stoppen, das hat bei mir funktioniert..

                            Ich mach spätestens am Montag weiter.. 😃

                            Vielen Dank für das Testen!

                            1 Antwort Letzte Antwort
                            0
                            • David G.D David G.

                              @ilovegym

                              Die neue Version läuft.
                              Top.

                              Was ich mir wünschen würde:

                              • Discord deaktivieren zu können (hab es mir einfach unten rausgelöscht).
                              • Ggf Telegram hinzufügen (natürlich auch deaktivierbar)
                              • Created auch als TS (Da schaue ich, wann das letzte Update war um Container zu finden die nicht mehr gepflegt werden)

                              EDIT
                              Soll control die Container updaten?
                              Falls ja klappt es bei mir nicht.

                              EDIT 2
                              Wenn ich control in einem Container drücke, startet meine JS Instanz neu.

                              ilovegymI Online
                              ilovegymI Online
                              ilovegym
                              schrieb am zuletzt editiert von ilovegym
                              #14

                              @david-g

                              Hi David,
                              heute ist Montag und wie versprochen, eine neue Version, aktuell V3.3 - ist alles in ein Script gekommen und es gibt jede Menge neue Optionen, siehe Changelog :)

                              Viel Spass damit! (aktuelles Script im ersten Beitrag)

                              1 Antwort Letzte Antwort
                              0
                              Antworten
                              • In einem neuen Thema antworten
                              Anmelden zum Antworten
                              • Älteste zuerst
                              • Neuste zuerst
                              • Meiste Stimmen


                              Support us

                              ioBroker
                              Community Adapters
                              Donate

                              862

                              Online

                              32.4k

                              Benutzer

                              81.5k

                              Themen

                              1.3m

                              Beiträge
                              Community
                              Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                              ioBroker Community 2014-2025
                              logo
                              • Anmelden

                              • Du hast noch kein Konto? Registrieren

                              • Anmelden oder registrieren, um zu suchen
                              • Erster Beitrag
                                Letzter Beitrag
                              0
                              • Home
                              • Aktuell
                              • Tags
                              • Ungelesen 0
                              • Kategorien
                              • Unreplied
                              • Beliebt
                              • GitHub
                              • Docu
                              • Hilfe