Questo tool, similmente al precedente Tool SharePoint API Demo (SPFx), permette di visualizzare delle informazioni relative ad un flow Power Automate.

Vengono visualizzate le seguenti informazioni:
  • JSON del flow
  • rappresentazione ad albero del JSON del flow
  • tabella con le connessioni usate nel flow
  • rappresentazione ad albero delle azioni del flow
  • elenco delle variabile e conteggio
  • elenco delle azioni e conteggio
Come il precedente tool non richiede installazione o priviligi particolari nel tenat, è sufficiente avere il permesso di accedere alla pagina di edit del flow https://make.powerautomate.com.

Il tool è realizzato in JavaScript puro (Vanilla JS) che può essere incollato nella Developer toolbar (F12 o CTRL+SHFT+I) del Browser all'interno di una pagina di edit di Power Automate.
Interfaccia utente
Interfaccia utente
Actions
Actions
Il vantaggio di questo approccio è che non richiede login aggiuntivi o AppRegistration, lavora nel contesto utente loggato in Power Automate.
Se compare un errore di autenticazione, ricaricare la pagina e incollare di nuovo lo sscript.

Come usarlo

il codice da incollare nella Developer toolbar è questo:

JavaScript: Codice JavaScript da incollare nel browser

(function () {
    /*
        Power Automate Tool Info (Sgart.it)
        https://www.sgart.it/IT/informatica/tool-power-automate-show-info-vanilla-js/post

        javascript:(function(){var s=document.createElement('script');s.src='/SiteAssets/ToolApiDemo/sgart-pa-tool-info.js?t='+(new Date()).getTime();document.head.appendChild(s);})();

        se compare {
            "error": {
                "code": "AuthenticationFailed",
                "message": "Authentication failed. "
            }
        } allora il token non è valido, ricaricare la pagina e rieseguire lo script
    */
    const VERSION = "1.2025-11-30.beta";

    const LOG_SOURCE = "Sgart.it:PowerAutomate:ToolInfo:";
    const LOG_COLOR_SOURCE = "%c" + LOG_SOURCE;
    const LOG_COLOR_LOG = "color: #000; background: #5cb85c; padding: 1px 4px;";
    const LOG_COLOR_DEBUG = "color: #000; background: #5bc0de; padding: 1px 4px;";
    const LOG_COLOR_INFO = "color: #000; background: #5cb85c; padding: 1px 4px;";
    const LOG_COLOR_WARN = "color: #000; background: #f0ad4e; padding: 1px 4px";
    const LOG_COLOR_ERROR = "color: #fff; background: #d9534f; padding: 1px 4px";

    const HTML_ID_WRAPPER = "sgart-content-wrapper";

    const HTML_ID_PUPUP = "sgart-popup";

    const HTML_ID_BTN_EXIT = "sgart-btn-exit";
    const HTML_ID_BTN_CLEAR_OUTPUT = "sgart-btn-clear-output";
    const HTML_ID_BTN_COPY_OUTPUT = "sgart-btn-copy-output";
    const HTML_ID_BTN_HISTORY = "sgart-btn-history";
    const HTML_ID_HTTP_STATUS = "sgart-http-status";
    const HTML_ID_HTTP_EXECUTION_TIME = "sgart-http-execution-time";
    const HTML_ID_HTTP_WAIT = "sgart-http-wait";

    const HTML_ID_BTN_EXECUTE = "sgart-btn-execute";
    const HTML_ID_TXT_INPUT = "sgart-txt-input";

    const TAB_OUTPUT = '-output';
    const TAB_KEY_RAW = 'sgart-tab-raw';
    const TAB_KEY_TREE = 'sgart-tab-tree';
    const TAB_KEY_TABLE_CONN_REF = 'sgart-tab-table-conn-ref';
    const TAB_KEY_TABLE_CONN_INST = 'sgart-tab-table-conn-inst';
    const TAB_KEY_TREE_FLOW_INFO = 'sgart-tab-tree-flow-info';
    const TAB_KEY_TABLE_VARIABLES = 'sgart-tab-table-variables';
    const TAB_KEY_TABLE_ACTIONS = 'sgart-tab-table-actions';
    const globalTabs = [
        { key: TAB_KEY_RAW, controlId: TAB_KEY_RAW + TAB_OUTPUT, type: "txt", text: "RAW", description: "API response", iconSVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M896 512H0V384h896v128zM384 768h896v128H384V768zm1024 0h640v128h-640V768zm640-384v128H1024V384h1024zM384 1152h1280v128H384v-128zM0 1536h1280v128H0v-128z"></path></svg>` },
        { key: TAB_KEY_TREE, controlId: TAB_KEY_TREE + TAB_OUTPUT, type: "tree", text: "Tree", description: "API response formatted as tree", iconSVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M512 384h1536v128H512V384zm512 640V896h1024v128H1024zm0 512v-128h1024v128H1024zM0 640V256h384v384H0zm128-256v128h128V384H128zm384 768V768h384v384H512zm128-256v128h128V896H640zm-128 768v-384h384v384H512zm128-256v128h128v-128H640z"></path></svg>` },
        { key: TAB_KEY_TABLE_CONN_REF, controlId: TAB_KEY_TABLE_CONN_REF + TAB_OUTPUT, type: "table", text: "Conn. Ref.", description: "Connection reference", iconSVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M256 1216q0-89 34-171t97-146l227-226 633 633-226 227q-63 63-145 97t-172 34q-73 0-141-22t-127-67l-327 326-90-90 326-327q-44-58-66-126t-23-142zm448 320q64 0 122-24t104-70l136-136-452-452-136 136q-45 45-69 103t-25 123q0 66 25 124t68 102 102 69 125 25zm871-1100q44 58 66 126t23 142q0 89-34 171t-97 146l-227 226-633-633 226-227q63-63 145-97t172-34q73 0 141 22t127 67l327-326 90 90-326 327zm-133 494q45-45 69-103t25-123q0-66-25-124t-69-101-102-69-124-26q-64 0-122 24t-104 70L854 614l452 452 136-136z"></path></svg>` },
        { key: TAB_KEY_TABLE_CONN_INST, controlId: TAB_KEY_TABLE_CONN_INST + TAB_OUTPUT, type: "table", text: "Conn. Install.", description: "Connection installed", iconSVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1300 1201l-271 271 90 90-226 227q-63 63-145 97t-172 34q-73 0-141-22t-127-67l-199 198-90-90 198-199q-44-58-66-126t-23-142q0-89 34-171t97-146l227-226 90 90 271-271 91 90-272 272 272 272 272-272 90 91zm-724 591q64 0 122-24t104-70l136-136-452-452-136 136q-45 45-69 103t-25 123q0 66 25 124t68 102 102 69 125 25zM1831 308q44 58 66 126t23 142q0 89-34 171t-97 146l-227 226-633-633 226-227q63-63 145-97t172-34q73 0 141 22t127 67l199-198 90 90-198 199zm-133 494q45-45 69-103t25-123q0-66-25-124t-69-101-102-69-124-26q-64 0-122 24t-104 70l-136 136 452 452 136-136z"></path></svg>` },
        { key: TAB_KEY_TREE_FLOW_INFO, controlId: TAB_KEY_TREE_FLOW_INFO + TAB_OUTPUT, type: "tree", text: "Flow", description: "Flow actions as tree", iconSVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1408 1152h640v640h-640v-256H992l-416 416-480-480 416-416V640H256V0h640v640H640v416l352 352h416v-256zM384 128v384h384V128H384zm192 1632l288-288-288-288-288 288 288 288zm1344-96v-384h-384v384h384z"></path></svg>` },
        { key: TAB_KEY_TABLE_VARIABLES, controlId: TAB_KEY_TABLE_VARIABLES + TAB_OUTPUT, type: "table", text: "Variables", description: "Flow variables (InitializeVariable) and use count (BETA)", iconSVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M139 960q0 223 59 430t165 402h-97q-62-91-111-191t-83-206-53-216T0 959q0-230 71-435t195-396h97q-55 108-96 205t-70 195-43 204-15 228zm1485-478q0 48-25 72t-73 24q-17 0-34-5t-33-11-33-11-34-5q-21 0-45 16t-49 42-50 58-46 64-38 60-26 45l101 426q3 13 8 35t14 45 21 39 30 17q23 0 50-20t53-48 45-58 29-50l43 21q-12 23-32 54t-46 64-54 65-61 57-61 42-59 16q-39 0-66-23t-45-55q-8-14-18-45t-20-71-22-85-20-86-16-76-11-54q-26 48-62 108t-78 122-86 115-85 88q-32 26-68 44t-78 18q-24 0-45-7t-38-22-27-35-10-45q0-42 26-69t68-27q22 0 41 11t35 23 31 24 27 11q18 0 47-28t63-72 69-97 66-103 52-90 32-59q-10-38-20-75t-19-75q-8-32-17-72t-22-81-33-75-47-55q-31-22-64-28t-71-6q-33 0-66 3v-44l332-59q40 43 65 85t42 85 29 91 28 102q32-48 64-98t69-95 80-84 98-66q18-9 38-14t40-6q22 0 43 6t38 18 26 30 10 44zm424 477q0 110-18 220t-53 215-84 206-111 192h-97q106-195 165-402t59-430q0-146-25-290t-78-282q-26-67-57-131t-64-129h97q124 191 195 396t71 435z"></path></svg>` },
        { key: TAB_KEY_TABLE_ACTIONS, controlId: TAB_KEY_TABLE_ACTIONS + TAB_OUTPUT, type: "table", text: "Actions", description: "Flow actions groupped with count", iconSVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1664 0v128H0V0h1664zm-649 512l-67 128H0V512h1015zM0 1024h747l-67 128H0v-128zm1512 0h568L1004 2048H747l304-640H691l535-1024h612l-326 640zm-559 896l807-768h-456l325-640h-325l-402 768h351l-304 640h4z"></path></svg>` },
    ];
    let currentTabKey = globalTabs[0].key;


    const console = {
        log: (msg, value) => {
            if (value)
                window.console.log(LOG_COLOR_SOURCE, LOG_COLOR_LOG, msg, value);
            else
                window.console.log(LOG_COLOR_SOURCE, LOG_COLOR_LOG, msg);
        },
        debug: (msg, value) => {
            if (value)
                window.console.debug(LOG_COLOR_SOURCE, LOG_COLOR_DEBUG, msg, value);
            else
                window.console.debug(LOG_COLOR_SOURCE, LOG_COLOR_DEBUG, msg);
        },
        info: (msg, value) => {
            if (value)
                window.console.info(LOG_COLOR_SOURCE, LOG_COLOR_INFO, msg, value);
            else
                window.console.info(LOG_COLOR_SOURCE, LOG_COLOR_INFO, msg);
        },
        warn: (msg, value) => {
            if (value)
                window.console.warn(LOG_COLOR_SOURCE, LOG_COLOR_WARN, msg, value);
            else
                window.console.warn(LOG_COLOR_SOURCE, LOG_COLOR_WARN, msg);
        },
        error: (msg, value) => {
            if (value)
                window.console.error(LOG_COLOR_SOURCE, LOG_COLOR_ERROR, msg, value);
            else
                window.console.error(LOG_COLOR_SOURCE, LOG_COLOR_ERROR, msg);
        }
    };

    const getElmById = (id) => { const elm = document.getElementById(id); return elm };
    const setElmByIdHtml = (id, html) => { const elm = document.getElementById(id); elm.innerHTML = html; return elm };
    const setElmByIdValue = (id, value) => { const elm = document.getElementById(id); elm.value = value; return elm };
    const showElmById = (id, flagShow) => { const elm = document.getElementById(id); elm.style.display = flagShow === true ? '' : 'none'; return elm };

    // encode dei caratteri in html
    String.prototype.htmlEncode = function () {
        const node = document.createTextNode(this);
        return document.createElement("a").appendChild(node).parentNode.innerHTML.replace(/'/g, "&#39;").replace(/"/g, "&#34;");
    };

    const copyToClipboard = (text) => navigator.clipboard.writeText(text)
        .then(() => console.debug('Text copied to clipboard:', text))
        .catch(err => console.error('Failed to copy text: ', err));

    const formatObjectAsHtmlTree = (objJson, idContainer, options) => {
        const BASE = "sgart-it-format-json-to-tree";
        const SVG_ADD = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M2048 960v128h-960v960H960v-960H0V960h960V0h128v960h960z"></path></svg>`;
        const SVG_SUB = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M0 960h2048v128H0V960z"></path></svg>`

        const getSequence = () => "id" + Math.random().toString(16).slice(2);
        const htmlEscape = (str) => (str ?? "").toString().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");

        const injectStyle = () => {
            const color = options || {};
            const className = `${BASE}-inject-styles`;
            const stylePrev = document.head.getElementsByClassName(className)[0];
            if (!stylePrev) {
                const css = `
        .${BASE} { 
            --${BASE}-prop: ${color.cProp ?? "#0451a5"};
            --${BASE}-sep: ${color.cSep ?? "#444"};
            --${BASE}-string: ${color.cString ?? "#a31515"};
            --${BASE}-number: ${color.cNumber ?? "#098658"};
            --${BASE}-boolean: ${color.cBoolean ?? "#0000ff"};
            --${BASE}-type: ${color.cType ?? "#666"};
            --${BASE}-btn: ${color.cBtn ?? "#222"};
        }
        .${BASE}, .${BASE} * { font-family: consolas, menlo, monaco, "Ubuntu Mono", source-code-pro, monospace; font-size: .9rem; }
        .${BASE} var, .${BASE} i, .${BASE} em { font-style: italic; text-decoration: none; font-weight: normal; color: var(--${BASE}-type); }
        .${BASE} i { padding: 0 5px 0 0; font-style: normal;  color: var(--${BASE}-sep);}
        .${BASE} label { display: inline; font-style: normal; text-decoration: none; font-weight: bold; padding: 0; }
        .${BASE} .button { display: inline-flex; justify-content: center; align-items: center; width: 24px; height: 24px; padding: 0; margin: 0 5px 0 0; border-radius: 0; border: 1px solid var(--${BASE}-btn); color: var(--${BASE}-btn); background-color: transparent; overflow: hidden; font-size: 1rem; cursor: pointer;}
        .${BASE} .button svg { width: 11px; height: 11px; pointer-events: none; fill: var(--${BASE}-btn);}
        .${BASE} ul { list-style: none; }
        .${BASE} ul li { min-height: 30px; line-height: 30px; vertical-align: middle; }
        .${BASE} label { color: var(--${BASE}-prop); }
        .${BASE} .key-value-boolean span, .${BASE} .key-value-null span, .${BASE} .key-value-undefined span { color: var(--${BASE}-boolean); }
        .${BASE} .key-value-string span { color: var(--${BASE}-string); }
        .${BASE} .key-value-number span { color: var(--${BASE}-number); }            
        `;
                const style = document.createElement('style');
                style.className = className;
                style.appendChild(document.createTextNode(css));
                document.head.appendChild(style);
            }
        };

        const getType = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;

        const formatObject = (obj, level) => {
            const objectName = Array.isArray(obj) ? "array" : "object";
            const items = Object.entries(obj);
            const s = items.reduce((accumulator, current) => {
                const [key, value] = current;
                const type = getType(value);
                if (type === "array" || type === "object")
                    return accumulator + `<li class="key-value-${type}" title="${type}"><label>${key}</label><i>:</i>${formatObject(value, level + 1)}</li>`;

                const str = htmlEscape(value);
                const strTitle = `${key} : ${type} = ${type === "string" ? `&quot;${str}&quot; {${str.length}}` : str}`;
                return accumulator + `<li class="key-value-${type}" title="${strTitle}"><label>${key}</label><i>:</i><span>${str}</span></li>`;
            }, "");
            const id = `${BASE}-${level}-${getSequence()}`;
            return `<button class="button" type="button" aria-expanded="true" aria-controls="${id}" data-tree-expand="true">${SVG_SUB}</button><em>${objectName}</em> <var>{${items.length}}</var><ul id="${id}">${s}</ul>`;
        };

        const format = (obj) => obj === null ? "null" : typeof obj === 'object' ? formatObject(obj, 0) : "Unsupported data type";

        const handleClick = (event) => {
            const btn = event.target;
            const ctrlId = btn.getAttribute("aria-controls");
            const control = getElmById(ctrlId);
            const isShow = control.style.display === "";
            control.style.display = isShow ? "none" : "";
            btn.setAttribute("aria-expanded", !isShow);
            btn.innerHTML = isShow ? SVG_ADD : SVG_SUB;
        };

        injectStyle();

        const str = `<section id="${BASE}" class="${BASE}">${format(objJson)}</section>`;
        if (idContainer) {
            const htmlContainer = getElmById(idContainer);
            htmlContainer.innerHTML = str;
            htmlContainer.removeEventListener("click", handleClick);
            htmlContainer.addEventListener("click", handleClick);
        }
        return str;
    };

    const htmlTableFromJson = (function () {
        const buildTableItem = (item) => {
            const table = {
                columns: [
                    {
                        key: 'internalName',
                        name: 'InternalName',
                        fieldName: 'internalName',
                        minWidth: 250,
                        isRowHeader: true,
                        isResizable: true
                    },
                    {
                        key: 'value',
                        name: 'Value',
                        fieldName: 'value',
                        minWidth: 450,
                        isResizable: true
                    }
                ],
                items: []
            };

            if (item) {
                table.items = Object.keys(item).map(key => {
                    const value = item[key];
                    if (typeof value === 'object') {
                        return { internalName: key, value: JSON.stringify(value, null, 2) };
                    }
                    return { internalName: key, value };
                });
            }
            return table;
        };

        const buildTableItems = (items) => {
            const table = {
                columns: [],
                items: []
            };

            if (!items || items.length === 0) {
                return table;
            }

            const item = items[0];
            if (item) {
                table.columns = Object.keys(item).map(key => ({
                    key,
                    name: key,
                    fieldName: key,
                    minWidth: 50,
                    isResizable: true
                }));

                const newItems = [];
                items.forEach(item => {
                    const ni = {};
                    Object.keys(item).forEach(key => {
                        const value = item[key];
                        ni[key] = typeof value === 'object'
                            ? JSON.stringify(value, null, 2)
                            : value;
                    });
                    newItems.push(ni);
                });
                table.items = newItems;

            }
            return table;
        };

        const buildTable = (items) => {
            if (!items) {
                return { columns: [], items: [] };
            }
            if (Array.isArray(items)) {
                return buildTableItems(items);
            }
            return buildTableItem(items);
        };

        const renderTable = (table, addCountRow) => {
            let html = '<table border="1" cellpadding="5" cellspacing="0"><thead><tr>';
            if (addCountRow === true) {
                html += `<th>Nr.</th>`;
            }
            table.columns.forEach(col => {
                html += `<th>${col.name}</th>`;
            });
            html += '</tr></thead><tbody>';
            table.items.forEach((item, index) => {
                html += '<tr>';
                if (addCountRow === true) {
                    html += `<td>${index + 1}</td>`;
                }
                table.columns.forEach(col => {
                    try {
                        const v = item[col.fieldName];
                        if (typeof v === 'string') {
                            if (v.length > 0 && ((v.startsWith('{') && v.endsWith('}')) || (v.startsWith('[') && v.endsWith(']')))) {
                                html += `<td data-value='${v.htmlEncode()}'>object</td>`;
                            } else {
                                html += `<td>${v.htmlEncode()}</td>`;
                            }
                        } else {
                            html += `<td>${v}</td>`;
                        }
                    } catch (error) {
                        console.error("Error rendering table cell:", error);
                    }
                });
                html += '</tr>';
            });
            html += '</tbody></table>';
            return html;
        };

        const buid = (json, addCountRow) => {
            const tableItems = buildTable(json);
            const html = renderTable(tableItems, addCountRow);
            return html;
        };

        return {
            buid
        };
    })();

    const BASE = "sgart-it-pa-tool-info-wrapper-1sdfy23";
    const injectStyle = () => {
        const css = `
            :root{
            --sgart-primary-color: #8a2e11;
            --sgart-primary-color-light: rgba(138,46,17,0.45);
            --sgart-primary-color-hover: #6f250e;
            --sgart-primary-color-dark: #4c1609;
            --sgart-secondary-color: #0a0a0a;
            --sgart-secondary-color-dark: #060606;
            --sgart-secondary-color-white: #ffffff;
            --sgart-secondary-color-gray-light: #e6e6e6;
            --sgart-btn-color-execute: #f0ad4e;
            --sgart-btn-color-execute-border: #aa6708;
            }
            .${BASE} { font-family: Arial, sans-serif; font-size: 14px; font-weight: normal; line-height: 1.6; border: 0; display: flex; flex-direction: column; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--sgart-secondary-color-white); color: var(--sgart-secondary-color-dark); margin: 0; padding: 0; z-index: 10000; box-sizing: border-box; }   
            .${BASE} input, .${BASE} textarea, .${BASE} select, .${BASE} .sgart-button { height: 32px; padding: 0 10px; border: 1px solid var(--sgart-primary-color); background-color: var(--sgart-secondary-color-white); box-sizing: border-box; background-image: none; border-radius: 2px;}
            .${BASE} select { width: 180px; }
            .${BASE} #sgart-api-demo { width: 200px; }
            .${BASE} .sgart-button  { background-color: var(--sgart-primary-color); color: var(--sgart-secondary-color-white); padding: 0 10px; cursor: pointer; width: 110px; overflow: hidden; white-space: nowrap; display: flex; align-items: center; justify-content: center; gap: 5px; }
            #${HTML_ID_BTN_EXECUTE} { background-color: var(--sgart-btn-color-execute); border: 1px solid var(--sgart-btn-color-execute-border); color: var(--sgart-secondary-color-dark)}
            #${HTML_ID_BTN_EXECUTE} svg { fill: var(--sgart-secondary-color-dark);}
            .${BASE} .sgart-button svg { color: var(--sgart-secondary-color-white); fill: var(--sgart-secondary-color-white); height: 20px; width: 20px; }
            .${BASE} .sgart-button.sgart-button-tab { background-color: var(--sgart-primary-color-light); color: var(--sgart-secondary-color); }
            .${BASE} .sgart-button.sgart-button-tab svg { fill: var(--sgart-secondary-color); }
            .${BASE} .sgart-button.selected, .${BASE} .sgart-button:hover, .${BASE} .sgart-button.sgart-button-tab.selected, .${BASE} .sgart-button.sgart-button-tab:hover { background-color: var(--sgart-primary-color-hover); color: var(--sgart-secondary-color-white); font-weight: bold; }
            .${BASE} .sgart-button.selected svg { fill: var(--sgart-secondary-color-white); }
            .${BASE} .sgart-button.sgart-button-tab { width: 120px }
            .${BASE} .sgart-button.sgart-button-tab:hover { border-color: var(--sgart-secondary-color); }
            .${BASE} .sgart-button.sgart-button-tab:hover svg { fill: var(--sgart-secondary-color-white); }
            .${BASE} .sgart-separator { margin: 0; }
            .${BASE} .sgart-header { position: relative; background-color: var(--sgart-secondary-color); color: var(--sgart-secondary-color-white); padding: 5px 10px; border-bottom: 1px solid var(--sgart-secondary-color-gray-light); height: 40px; display: flex; flex-direction: row; align-items: center; justify-content: space-between;}     
            .${BASE} .sgart-header h1 { font-size: 1.2em; font-weight: bold; margin: 0; } 
            .${BASE} .sgart-header a { display: flex }
            .${BASE} .sgart-header .sgart-button { background-color: var(--sgart-secondary-color); border: 1px solid var(--sgart-secondary-color); color: var(--sgart-secondary-color-white); padding: 5px 10px; width: 80px; }
            .${BASE} .sgart-header .sgart-button:hover { border: 1px solid var(--sgart-secondary-color-white); font-weight: normal; }
            .${BASE} .sgart-header .logo { height: 33px; margin-right: 10px; }
            .${BASE} .sgart-toolbar { display:flex; flex-direction: row; align-items: center; justify-content: space-between; }
            .${BASE} .sgart-toolbar-left { display: flex; gap: 10px; justify-content: left; align-items: center; flex-wrap: wrap; }
            .${BASE} .sgart-toolbar-right{ justify-content: right; }
            .${BASE} .sgart-body { display: flex; flex-direction: column; flex-grow: 1; padding: 10px; gap: 10px; font-weight: normal; }
            .${BASE} .sgart-body label { font-weight: normal; padding: 0; text-wrap-mode: nowrap; }   
            .${BASE} .sgart-input-area { display: flex; gap: 10px; align-items: center; justify-content: space-between; }
            .${BASE} .sgart-input { flex-grow: 1; }
            .${BASE} .sgart-output-area { flex-grow: 1; display: flex; overflow: hidden; position: relative; }
            .${BASE} .sgart-output-area > div {position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow: auto; flex-grow: 1; display: flex; box-sizing: border-box; border: 1px solid var(--sgart-primary-color); background-color: var(--sgart-secondary-color-white); }
            .${BASE} .sgart-output-area .sgart-output-tree { padding: .5em; }
            .${BASE} .sgart-output-area table { border-collapse: collapse; width: 100%; background-color: var(--sgart-secondary-color-white); color: var(--sgart-secondary-color-dark);}
            .${BASE} .sgart-output-txt, .${BASE} .sgart-output-table { width: 100%; height: auto; flex-grow: 1; gap: 10px; font-family: consolas, menlo, monaco, "Ubuntu Mono", source-code-pro, monospace; font-size: 14px; white-space: pre; box-sizing: border-box; border: none; }
            .${BASE} table th { background-color: var(--sgart-primary-color); color: var(--sgart-secondary-color-white); text-align: left; position: sticky; top: 0; z-index: 1000; padding: 5px;}
            .${BASE} .sgart-http-status { border: 1px solid var(--sgart-secondary-color); padding: 0; background-color: var(--sgart-secondary-color-gray-light); color: var(--sgart-secondary-color); font-weight: bold; display: inline-flex; width: 50px; height: 32px; align-items: center; justify-content: center; }
            .${BASE} .sgart-http-status-100 { background-color: #e7f3fe; color: #31708f; border-color: #bce8f1; }  
            .${BASE} .sgart-http-status-200 { background-color: #dff0d8; color: #3c763d; border-color: #d6e9c6; }
            .${BASE} .sgart-http-status-300 { background-color: #fcf8e3; color: #8a6d3b; border-color: #faebcc; }
            .${BASE} .sgart-http-status-400 { background-color: #f2dede; color: #a94442; border-color: #ebccd1; }
            .${BASE} .sgart-http-status-500 { background-color: #f2dede; color: #a94442; border-color: #ebccd1; }
            .${BASE} .sgart-http-wait { border: 1px solid var(--sgart-secondary-color); padding: 0; background-color: var(--sgart-secondary-color-gray-light); color: var(--sgart-secondary-color); font-weight: bold; display: inline-flex; width: 100px; height: 32px; align-items: center; justify-content: center; }
            .${BASE} .sgart-label-count { border: 1px solid var(--sgart-secondary-color-gray-light); padding: 0; background-color: var(--sgart-secondary-color-white); color: var(--sgart-secondary-color); font-weight: bold; display: inline-flex; width: 50px; height: 32px; align-items: center; justify-content: center; }
            .${BASE} .sgart-popup { position: fixed; display: none; /*flex;*/ top: 0; left: 0; right: 0; bottom: 0; backdrop-filter: blur(5px); z-index: 10001; padding: 40px 20px 20px 20px; }
            .${BASE} .sgart-popup .sgart-popup-wrapper { display: flex; flex-direction: column; width: 100%; background-color: var(--sgart-secondary-color-white); border: 2px solid var(--sgart-primary-color); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); z-index: 10002; }
            .${BASE} .sgart-popup .sgart-pupup-header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 10px; height: 40px; border-bottom: 1px solid var(--sgart-primary-color); background-color: var(--sgart-primary-color); color: var(--sgart-secondary-color-white); }
            .${BASE} .sgart-popup .sgart-popup-body { display: flex; flex-direction: column; padding: 10px; height: 100%; overflow-x: hidden; overflow-y: auto; }
            .${BASE} .sgart-popup h3 { margin: 0; font-size: 18px;}
            .${BASE} .sgart-popup .sgart-popup-group { display: block; padding: 10px; }
            .${BASE} .sgart-popup .sgart-popup-group > div { display: flex; flex-direction: row; justify-content: flex-start; padding: 10px; flex-wrap: wrap; }
            .${BASE} .sgart-popup .sgart-popup-action { border: 1px solid var(--sgart-primary-color); padding: 10px; margin: 5px; cursor: pointer; width: 32%; overflow: hidden; text-align: left; background-color: var(--sgart-secondary-color-white); }
            .${BASE} .sgart-popup .sgart-popup-action h4 { margin: 0 0 8px 0; font-size: 16px;}
            .${BASE} .sgart-popup .sgart-popup-action p { word-wrap: break-word; margin: 8px 0;}
            .${BASE} .sgart-popup .sgart-popup-action > div { word-wrap: break-word; margin: 8px 0;}
            .${BASE} .sgart-popup .sgart-popup-history li { display: flex; flex-direction: row; align-items: center; margin: 5px 0; gap: 10px; justify-content: space-between;}
            .${BASE} .sgart-popup .sgart-popup-history button { flex: auto;}
        `;
        const stylePrev = document.head.getElementsByClassName('sgart-inject-style')[0];
        if (stylePrev) {
            document.head.removeChild(stylePrev);
        }
        const style = document.createElement('style');
        style.className = 'sgart-inject-style';
        style.appendChild(document.createTextNode(css));
        document.head.appendChild(style);
    };

    const showInterface = () => {
        const interfaceDivPrev = getElmById(HTML_ID_WRAPPER);
        if (interfaceDivPrev) {
            document.body.removeChild(interfaceDivPrev);
        }
        const htmlTabs = globalTabs.reduce((accumulator, current) => {
            const k = current.key;
            const title = current.description.htmlEncode();
            const text = (current.iconSVG ?? "") + current.text.htmlEncode();
            return accumulator + `<button id="${k}" class="sgart-button sgart-button-tab" data-tab-key="${k}" data-tab-control-id="${k + TAB_OUTPUT}" title="${title}">${text}</button>`;
        }, "");
        const htmlOutputs = globalTabs.reduce((accumulator, current) => {
            return accumulator + `<div id="${current.key + TAB_OUTPUT}" class="sgart-output-${current.type}"></div>`;
        }, "");
        const interfaceDiv = document.createElement('div');
        interfaceDiv.id = HTML_ID_WRAPPER;
        interfaceDiv.className = BASE;
        interfaceDiv.innerHTML = `
            <div class="sgart-header">
                <a href="https://www.sgart.it/IT/informatica/???/post" target="_blank"><img alt="Logo Sgart.it" class="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJEAAAAhCAYAAADZEklWAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwAAADsABataJCQAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC42/Ixj3wAAAvtJREFUeF7tki2WFTEQhUvh2AAOxwZwODbAClCsAMcCUDjUbACFQ7EBHAqFYwMoHKpI9ZycE+rdVCXpSvebmYhv0qn7U336DfFf4kNglj+LewjxHzmxGMYROxanQfxbTiyGccSOxWkQ/5ITi5vWA+oQLG1x5yH+KScWTU0T1bOIR74/AnkHIP4hJxZNTRPVs4gHff/A34T4u5xYNDWNeC1QZvE/6LsJyNsD6vB6y/0lwEv8DQsblraIB33viN9AOhDIm0F6JUP8FQsblraIB33vs36Djnch/oKFDUtbxIO+91m/Qce7EH/GwoZoUazeW7zeUq95BO2zQHnB85R6zZMg/pQetDkavSNq50Pt7c0hf+C7jId7iHphzUPt7c0hf+C7EN/IsxKi0TvQTpl5oIx1zzMPlLHueeaBMvquKfWaR9A+C+TXs7I7U+o1T4L4Y3qIQi/NaA15rbzQkpE7ovRokK5nLR7NUZkWJr/LxWCYD3KAuaA15LXywkimhZbekd2iI5A3g3Qv08JIb0fmYjDMeznAXNAa8lp5YSTTQkvvrN2aWXtGejsyF4Nh3skB5oLWkNfKCy0ZuY9QduQe616b7WXWnpHejgzx23RagBDE8moNeb1dLRmvo5WW3qhdJbP2jPR2ZIjfYGHD0jQ9Pcjr7WrJ1DweKGPda7O9zNoz0tuRIX6NhQ1L0/T0IK+3qyVzTb0jzNoz0tuRsctE6wF1CFpDXisvtGSuqXeEWXtGejsyxK+wEIregXbKzANlrHueeaCMda/N9lLbg/A8Wi/vtVlJLQMgfpketDkavSNq55m9UbtKWju99/PutVmJp2eSr928B70jaueZvVG7Slo7vffz7rVZiadnko/4RXo+gnIx0lsoO3KPvkfR0lt6IkA7ang5TxdKjwb5K6S/d4TncjTMFocDh1fJMzkaZovDgcPpPE0HAnkzSPcyi0OAw+k8kQPMPFBGzxaHA4fTeSwHmPcS1bPYBRxOZ/0T3SvgcDqP0hEF6l8cCNM/xi1s5uHihBcAAAAASUVORK5CYII="></a>
                <h1>Tool Power Automate Info (Vanilla JS)</h1>
                <button id="${HTML_ID_BTN_EXIT}" class="sgart-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M128 128h1792v1792H128V128zm1664 1664V256H256v1536h1536zM621 1517l-90-90 402-403-402-403 90-90 403 402 403-402 90 90-402 403 402 403-90 90-403-402-403 402z"></path></svg>Exit</button>
            </div>
            <div class="sgart-body">
                <div class="sgart-input-area">
                    <label for="${HTML_ID_TXT_INPUT}">API url:</label>
                    <input type="text" id="${HTML_ID_TXT_INPUT}" class="sgart-input" value="web/lists">
                </div>
                <div class="sgart-toolbar">
					<div class="sgart-toolbar-left">
						<button id="${HTML_ID_BTN_EXECUTE}" class="sgart-button" title="Execute api call"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1792 1024L512 1920V128l1280 896zM640 1674l929-650-929-650v1300z"></path></svg><span>Execute</span></button>
						<span class="sgart-separator">|</span>
						<button id="${HTML_ID_BTN_CLEAR_OUTPUT}" class="sgart-button" title="Clear all outputs"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1115 1792h421v128H453L50 1516q-24-24-37-56t-13-68q0-35 13-67t38-58L1248 69l794 795-927 928zm133-1542L538 960l614 613 709-709-613-614zM933 1792l128-128-613-614-306 307q-14 14-14 35t14 35l364 365h427z"></path></svg><span>Clear</span></button>
						<button id="${HTML_ID_BTN_COPY_OUTPUT}" class="sgart-button" title="Copy current response"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1920 805v1243H640v-384H128V0h859l384 384h128l421 421zm-384-37h165l-165-165v165zM640 384h549L933 128H256v1408h384V384zm1152 512h-384V512H768v1408h1024V896z"></path></svg><span>Copy</span></button>
						<span class="sgart-separator">|</span>
                        ${htmlTabs}
                        <span class="sgart-separator">|</span>
                        <button id="${HTML_ID_BTN_HISTORY}" class="sgart-button" title="Show popup with histories"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1024 512v549l365 366-90 90-403-402V512h128zm944 113q80 192 80 399t-80 399q-78 183-220 325t-325 220q-192 80-399 80-174 0-336-57-158-55-289-156-130-101-223-238-47-69-81-144t-57-156l123-34q40 145 123 266t198 208 253 135 289 48q123 0 237-32t214-90 182-141 140-181 91-214 32-238q0-123-32-237t-90-214-141-182-181-140-214-91-238-32q-130 0-252 36T545 268 355 429 215 640h297v128H0V256h128v274q17-32 37-62t42-60q94-125 220-216Q559 98 710 49t314-49q207 0 399 80 183 78 325 220t220 325z"></path></svg><span>History</span></button>
                        <span class="sgart-separator">|</span>
                        <span><label>Status:</label> <span id="${HTML_ID_HTTP_STATUS}" class="sgart-http-status" title="HTTP response status"></span></span>
                        <span><label>Time:</label> <span id="${HTML_ID_HTTP_EXECUTION_TIME}" class="sgart-http-execution-time" title="HTTP execution time"></span></span>
                        <span id="${HTML_ID_HTTP_WAIT}" class="sgart-http-wait">Waiting...</span>
    				</div>
					<div class="sgart-toolbar-right"><small>v. ${VERSION}</small></div>
                </div>
                <div class="sgart-output-area"><div>${htmlOutputs}</div></div>
            </div>
            <div id="${HTML_ID_PUPUP}" class="sgart-popup"></div>            
        `;
        document.body.appendChild(interfaceDiv);
    };

    /* FETCH JSON POWER AUTOMATE */

    /**
     * verificare se c'è un metodo migliore per ricavare environment
     */
    const getEnvironmentId = () => {
        const m = window.location.pathname.match(/\/environments\/([a-z0-9\-]+)\//i);
        return m.length === 2 ? m[1] : undefined;
    };

    const getFlowId = () => {
        const m = window.location.pathname.match(/\/flows\/([a-z0-9\-]+)/i);
        return m.length === 2 ? m[1] : undefined;
    };

    const getFlowUrl = () => {
        try {
            const env = getEnvironmentId().replace(/-/g, "");
            console.debug('env', env);
            const env1 = env.substring(0, env.length - 2);
            console.debug('env1', env1);
            const env2 = env.substring(env.length - 2);
            console.debug('env2', env2);
            const flowId = getFlowId();
            console.debug('flowId', flowId);
            return `https://${env1}.${env2}.environment.api.powerplatform.com/powerautomate/flows/${flowId}?api-version=1&$expand=properties.connectionreferences.apidefinition,properties.definitionsummary.operations.apioperation,operationDefinition,plan,properties.throttleData,properties.estimatedsuspensiondata,properties.licenseData,properties.billingContext,properties.throttlingBehavior,properties.powerFlowType,properties.protectionStatus,properties.owningUser&draftFlow=true`;
        } catch (error) {
            console.error('getFlowUrl', error);
        }
        return undefined;
    };

    const getToken = () => {
        try {
            for (let i = 0; i < localStorage.length; i++) {
                const k = localStorage.key(i);
                const v = localStorage.getItem(k);
                if (k.indexOf('-login.windows.net-accesstoken-') >= 0 && k.indexOf('-https://service.flow.microsoft.com//user_impersonation https://service.flow.microsoft.com//.default--') >= 0) {
                    const obj = JSON.parse(v);
                    return obj.secret;
                }
            }
        } catch (error) {
            console.error('getToken', error);
        }
        return undefined;
    };

    const fetchGetJson = async (url) => {
        try {
            const token = getToken();
            console.debug('token', token.substring(0, 20) + "..." + token.substring(token.length - 20));
            const ct = "application/json";
            const response = await fetch(url, { method: "GET", headers: { "Authorization": `Bearer ${token}`, "Accept": ct, "Content-Type": ct } });
            const data = await response.json();
            return {
                status: response.status,
                data: data ?? {}
            };
        } catch (error) {
            console.error("fetchGetJson: Error parsing JSON response:", error);
            return {
                status: response.status,
                data: { error: error.message }
            };
        }
    };

    /* END: FETCH JSON POWER AUTOMATE */

    /* History */

    const history = (function () {
        const LOCAL_STORAGE_KEY_HISTORY = "sgart_it_pa_tool_info_history_v1";
        const MAX_HISTORY_ITEMS = 99;
        const historyList = [];

        const loadFromStorage = () => {
            try {
                const historyJson = localStorage.getItem(LOCAL_STORAGE_KEY_HISTORY);
                if (historyJson) {
                    const historyArray = JSON.parse(historyJson);
                    if (Array.isArray(historyArray)) {
                        historyList.length = 0;
                        historyArray.forEach(item => historyList.push(item));
                    }
                }
            } catch (error) {
                console.error("Error loading history from local storage:", error);
            }
        };

        const saveToStorage = () => {
            try {
                const historyJson = JSON.stringify(historyList);
                localStorage.setItem(LOCAL_STORAGE_KEY_HISTORY, historyJson);
            } catch (error) {
                console.error("Error saving history to local storage:", error);
            }
        };

        const clear = () => {
            historyList.length = 0;
            saveToStorage();
        };

        const getList = () => {
            if (historyList.length === 0) {
                loadFromStorage();
            }
            return historyList;
        };

        const add = (url) => {
            if (historyList.length > 0 && historyList[0].url.toLocaleLowerCase() === url.toLocaleLowerCase()) {
                // do nothing, same as last     
            } else {
                if (historyList.length >= MAX_HISTORY_ITEMS) {
                    historyList.pop();
                }
                const historyItem = {
                    url: url,
                    timestamp: new Date().toISOString(),
                    //response: response
                };
                historyList.unshift(historyItem);
                saveToStorage();
            }
        };

        return {
            init: loadFromStorage,
            clear,
            getList,
            add
        };
    })();

    /* END History */


    /* POPUP */

    const EVENT_CLOSE = "popup-close";
    const EVENT_SET_URL = "set-url";

    const popup = (function () {
        let fnHandleClick = undefined;

        const show = (title, bodyContentHtml, fnHandle) => {
            const elmPopup = getElmById(HTML_ID_PUPUP);
            elmPopup.innerHTML = `
                <div class="sgart-popup-wrapper">
                    <div class="sgart-pupup-header">
                        <h2>${title.htmlEncode()}</h2>
                        <button class="sgart-button sgart-popup-event" data-event="${EVENT_CLOSE}" title="close popup">Close</button>
                    </div>
                    <div class="sgart-popup-body">${bodyContentHtml}</div>
                </div>`;
            elmPopup.style.display = 'flex';
            elmPopup.addEventListener("click", fnHandle);
        };

        const hide = () => {
            const elmPopup = getElmById(HTML_ID_PUPUP);
            elmPopup.style.display = 'none';
            elmPopup.innerHTML = '';
            if (fnHandleClick && typeof fnHandleClick === 'function') {
                elmPopup.removeEventListener("click", fnHandleClick);
                fnHandleClick = undefined;
            }
        };

        return {
            show,
            hide
        };
    })();

    const handlePopupClickEvent = (event) => {
        const target = event.target;
        const actionElem = target.closest('.sgart-popup-event');
        if (actionElem) {
            const poupEvent = actionElem.getAttribute('data-event')
            if (poupEvent === EVENT_CLOSE) {
                popup.hide();
            } else if (poupEvent === EVENT_SET_URL) {
                const url = actionElem.getAttribute('data-url');
                setElmByIdValue(HTML_ID_TXT_INPUT, url);
                popup.hide();
                handleExecuteClickEvent();
            } else {
                console.error("Unknown popup event:", poupEvent);
            }
        }
    };

    const popupShowHistory = () => {
        let html = "";
        const historyList = history.getList();
        if (historyList.length === 0) {
            html = "<p>No history available.</p>";
        } else {
            html += "<div class='sgart-popup-history'><ol>";
            historyList.forEach((historyItem, index) => {
                const url = historyItem.url.htmlEncode();
                const date = `${new Date(historyItem.timestamp).toLocaleString()}`.htmlEncode();
                html += "<li><span>" + (index + 1) + "</span><span>" + date + "</span><button class='sgart-popup-action sgart-popup-event'"
                    + " data-event=\"" + EVENT_SET_URL + "\""
                    + " data-url=\"" + url + "\""
                    + "><strong>" + url + "</strong>"
                    + "</button></li>";
            });
            html += "</ol></div>";
        }
        popup.show("History", html, handlePopupClickEvent);
    };

    /* END POPUP */

    /* parse JSON flow info */

    const getFlowInfo = function (dataObject) {

        const getConnections = (objConnections) => {
            const result = [];
            try {
                for (const [key, value] of Object.entries(objConnections)) {
                    result.push({
                        key,
                        connectionName: value.connectionName,
                        id: value.apiDefinition.id,
                        name: value.apiDefinition.name,
                        type: value.apiDefinition.type,
                        displayName: value.apiDefinition.properties.displayName,
                        value
                    });
                }
            } catch (error) {
                console.error('Error in getConnections:', error);
                result.push({
                    displayName: "ERROR",
                    message: error.message
                });
            }
            return result;
        };

        /**
         * @description Extract flow actions information grupped with count
         * @param {Object} arrActions - Flow actions array
         */
        const getActionsAndCount = (arrActions) => {
            const result = [];
            try {
                for (const value of arrActions) {
                    const type = value.type;
                    const swaggerOperationId = value.swaggerOperationId ? value.swaggerOperationId : null;
                    const summary = value.apiOperation ? value.apiOperation.properties.summary : null;
                    const displayName = value.apiOperation ? value.apiOperation.properties.api.displayName : '';

                    const found = result.find(r => r.type === type && r.swaggerOperationId === swaggerOperationId);
                    if (found) {
                        found.count += 1;
                    } else {
                        result.push({
                            displayName,
                            type,
                            swaggerOperationId,
                            summary,
                            apiName: value.api ? value.api.name : null,
                            count: 1,
                            value
                        });
                    }
                }
                result.sort((a, b) => {
                    const a1 = a.displayName + '-' + a.type + '-' + a.swaggerOperationId;
                    const b1 = b.displayName + '-' + b.type + '-' + b.swaggerOperationId;
                    return a1 < b1 ? -1 : a1 > b1 ? 1 : 0;
                });
            } catch (error) {
                console.error('Error in getActionsAndCount:', error);
                result.push({
                    displayName: "ERROR",
                    message: error.message
                });
            }
            return result;
        }

        const getActionsTreeRecursive = (arrActions) => {
            if (arrActions == null || arrActions.length === 0) {
                return null;
            }
            const result = [];
            try {
                // leggo tutte le azioni
                for (const [key, value] of Object.entries(arrActions)) {
                    const runAfterKeys = value.runAfter ? Object.keys(value.runAfter) : [];
                    const runAfterName = runAfterKeys.length > 0 ? runAfterKeys[0] : '';
                    const runAfterCondition = runAfterKeys.length > 0 ? value.runAfter[runAfterName] : [];

                    const actionNode = {
                        name: key,
                        description: value.description,
                        type: value.type,
                        runAfter: {
                            name: runAfterName,
                            condition: runAfterCondition
                        }
                    };
                    if (value.actions) {
                        actionNode.actions = getActionsTreeRecursive(value.actions);
                    }
                    if (value.else) {
                        actionNode.else = getActionsTreeRecursive(value.else.actions);
                    }
                    result.push(actionNode);
                }
                // riordino le azioni in base alla dipendenza
                let node = result.find(r => r.runAfter.name === '');
                //console.log('node:', node);
                let i = 600; // safety counter to avoid infinite loops
                if (node && i > 0) {
                    const resultSorted = [];
                    while (node) {
                        i--;
                        resultSorted.push(node);
                        const actionName = node.name;
                        node = result.find(r => r.runAfter.name === actionName);
                    }
                    return resultSorted;
                } else {
                    return result;
                }
            } catch (error) {
                console.error('Error in getActionsTree:', error);
                result.error = {
                    displayName: "ERROR",
                    message: error.message
                };
            }
            return result;
        };

        const getActionsTree = (dataObject) => {
            const result = {
                name: 'ROOT',
                actions: getActionsTreeRecursive(dataObject.actions)
            };
            return result;
        };

        const getVariables = (dataObject) => {
            const result = [];
            try {
                const str = JSON.stringify(dataObject);
                for (const [key, value] of Object.entries(dataObject)) {
                    if (value.type === "InitializeVariable") {
                        const varValue = value.inputs.variables[0];
                        // TODO: migliorare regex per evitare falsi positivi
                        const re = RegExp(`@variables\\('${varValue.name}'\\)|@\\{variables\\('${varValue.name}'\\)\\}`, 'g');
                        result.push({
                            key,
                            name: varValue.name,
                            type: varValue.type,
                            value: varValue.value,
                            description: value.description,
                            count: ((str || '').match(re) || []).length
                        });
                    }
                }
            } catch (error) {
                console.error('Error in getVariables:', error);
                result.push({
                    displayName: "ERROR",
                    message: error.message
                });
            }
            return result;
        };

        const getInfo = (dataObject) => {
            try {
                const props = dataObject.properties;
                const result = {
                    id: dataObject.id,
                    name: dataObject.name,
                    type: dataObject.type,
                    createdTime: props.createdTime,
                    lastModifiedTime: props.lastModifiedTime,
                    apiId: props.apiId,
                    displayName: props.displayName,
                    description: props.definitionSummary.description,
                    userType: props.userType,
                    connectionReferences: getConnections(props.connectionReferences),
                    installedConnectionReferences: getConnections(props.installedConnectionReferences),
                    actions: getActionsAndCount(props.definitionSummary.actions),
                    tree: getActionsTree(props.definition),
                    variables: getVariables(props.definition.actions)
                };
                return result;
            } catch (error) {
                console.error('Error in getInfo:', error);
                return {
                    displayName: "ERROR",
                    message: error.message
                };
            }
        };

        return getInfo(dataObject);
    };
    /* end parse JSON flow info */

    const handleExecuteKeydownEvent = (event) => { if (event.keyCode === 13) handleExecuteClickEvent(); };

    const renderContent = (data) => {
        if (data) {
            const flowInfo = getFlowInfo(data);

            globalTabs.forEach(item => {
                const elm = getElmById(item.controlId);
                switch (item.key) {
                    case TAB_KEY_RAW:
                        elm.innerHTML = JSON.stringify(data, null, 2).htmlEncode();
                        break;
                    case TAB_KEY_TREE:
                        formatObjectAsHtmlTree(data, item.controlId);
                        break;
                    case TAB_KEY_TABLE_CONN_REF:
                        const tableRefHtml = htmlTableFromJson.buid(flowInfo.connectionReferences, true);
                        elm.innerHTML = tableRefHtml;
                        break;
                    case TAB_KEY_TABLE_CONN_INST:
                        const tableInstHtml = htmlTableFromJson.buid(flowInfo.installedConnectionReferences, true);
                        elm.innerHTML = tableInstHtml;
                        break;
                    case TAB_KEY_TABLE_VARIABLES:
                        const tableVarsHtml = htmlTableFromJson.buid(flowInfo.variables, true);
                        elm.innerHTML = tableVarsHtml;
                        break;
                    case TAB_KEY_TABLE_ACTIONS:
                        const tableActionsHtml = htmlTableFromJson.buid(flowInfo.actions, true);
                        elm.innerHTML = tableActionsHtml;
                        break;
                    case TAB_KEY_TREE_FLOW_INFO:
                        formatObjectAsHtmlTree(flowInfo.tree, item.controlId, true);
                        break;
                }
            });
        } else {
            globalTabs.forEach(item => setElmByIdHtml(item.controlId, "Error data null"));
        }
    };

    const handleExecuteClickEvent = () => {
        try {
            const input = getElmById(HTML_ID_TXT_INPUT).value;

            globalTabs.forEach(item => setElmByIdHtml(item.controlId, "Executing..."));

            setElmByIdHtml(HTML_ID_HTTP_STATUS, "...");
            setElmByIdHtml(HTML_ID_HTTP_EXECUTION_TIME, "-");
            showElmById(HTML_ID_HTTP_WAIT, true);

            const startTime = performance.now();

            globalTabs.forEach(item => setElmByIdHtml(item.controlId, ""));

            fetchGetJson(input).then(response => {
                const endTime = performance.now();
                setElmByIdHtml(HTML_ID_HTTP_EXECUTION_TIME, (Math.round((endTime - startTime) * 10) / 10) + " ms");

                const statusGroup = parseInt(response.status / 100).toString() + "00";
                setElmByIdHtml(HTML_ID_HTTP_STATUS, response.status).className = `sgart-http-status sgart-http-status-${statusGroup}`;


                history.add(input);

                renderContent(response.data);
                showElmById(HTML_ID_HTTP_WAIT, false);
            }).catch(error => {
                console.error("Error executing API request:", error);
                const msg = "Error: " + error.message;
                globalTabs.forEach(item => setElmByIdHtml(item.controlId, msg));
                showElmById(HTML_ID_HTTP_WAIT, false);
            });
        } catch (error) {
            console.error('handleExecuteClickEvent', error);
        }
    };

    const handleSwitchTabEvent = (event) => {
        const key = event.currentTarget.getAttribute('data-tab-key');

        globalTabs.forEach(item => {
            const btn = document.getElementById(item.key);
            btn.classList.remove('selected');

            const controlElem = document.getElementById(item.controlId);
            if (item.key === key) {
                btn.classList.add('selected');
                controlElem.style.display = 'block';
            } else {
                btn.classList.remove('selected');
                controlElem.style.display = 'none';
            }
        });
    };

    const handleExitClickEvent = () => {
        window.removeEventListener("beforeunload", handleBeforeunloadEvent);
        const interfaceDiv = document.getElementById(HTML_ID_WRAPPER);
        document.body.removeChild(interfaceDiv);
        const style = document.head.getElementsByClassName('sgart-inject-style')[0];
        if (style) {
            document.head.removeChild(style);
        }
        console.log("Interface closed");
    };

    const addEvents = () => {
        const btnExecute = document.getElementById(HTML_ID_BTN_EXECUTE);
        btnExecute.addEventListener("click", handleExecuteClickEvent);

        const txtInput = document.getElementById(HTML_ID_TXT_INPUT);
        txtInput.addEventListener("keydown", handleExecuteKeydownEvent);

        document.getElementById(HTML_ID_BTN_EXIT).addEventListener("click", handleExitClickEvent);
        document.getElementById(HTML_ID_BTN_HISTORY).addEventListener("click", popupShowHistory);
        document.getElementById(HTML_ID_BTN_CLEAR_OUTPUT).addEventListener("click", () => globalTabs.forEach(item => setElmByIdHtml(item.controlId, "")));
        document.getElementById(HTML_ID_BTN_COPY_OUTPUT).addEventListener("click", () => {
            console.log('copy-top', currentTabKey);
            console.log('copy-tabs', globalTabs);
            const tab = globalTabs[globalTabs.findIndex(item => item.key === currentTabKey)];
            console.log('copy', tab);
            if (tab) {
                copyToClipboard(getElmById(tab.controlId).innerHTML);
            } else {
                console.error('copy', 'element not found');
            }
        });

        const htmlTabs = document.getElementsByClassName('sgart-button-tab');
        Array.from(htmlTabs).forEach(btn => btn.onclick = handleSwitchTabEvent);
        htmlTabs[0].click();
    };

    const handleBeforeunloadEvent = (event) => {
        event.preventDefault();
        console.log("beforeunload", event);
    };

    const init = () => {
        console.log(`v.${VERSION} - https://www.sgart.it/IT/informatica/???/post`);

        injectStyle();
        showInterface();
        addEvents();

        history.init();

        window.addEventListener("beforeunload", handleBeforeunloadEvent);

        // set default
        const elmTxt = document.getElementById(HTML_ID_TXT_INPUT);
        elmTxt.value = getFlowUrl();
        elmTxt.focus();
        handleExecuteClickEvent();
    };

    init();
})();
L'ultima versione del codice JavaScript può essere scaricato da GitHub: sgart-vanillajs-sp-api-demo.
Il tool rimane attivo finchè non si cambia pagina oppure si preme il pulsante Exit.
La Developer Toolbar può essere chiusa per aumentare l'area di lavoro.

Su molti Browser, quando si cerca di incollare nella Developer Toolbar, compare questo messaggio:
Warning: Don’t paste code into the DevTools Console that you don’t understand or haven’t reviewed yourself. This could allow attackers to steal your identity or take control of your computer. Please type ‘allow pasting’ below and press Enter to allow pasting.
per abilitare l'incolla, scrivere allow pasting e premere invio.