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.7k

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

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

script Portainer Api V3.3 - inkl. html Tabelle

Geplant Angeheftet Gesperrt Verschoben Skripten / Logik
blocklyjavascriptmonitoring
14 Beiträge 3 Kommentatoren 633 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 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

                            411

                            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