🍬 Wrapper für FW_cmd, weniger Probleme wenn man Perlcode von FHEMWEB sendet

Begonnen von Torxgewinde, 27 März 2025, 09:06:25

Vorheriges Thema - Nächstes Thema

Torxgewinde

Ich möchte gerne Perlcode vom Webinterface aus an das Backend in FHEM zur Ausführung senden. Es gibt dazu die Javascript-Funktion FW_cmd (siehe Wiki).

Leider verschluckt sich die Funktion bzw. das Ajax dahinter an vielen Zeichen die in Perl vorkommen. Dies sind zum Beispiel häufige Dinge wie Leerzeichen, Zeilenumbrüche, also praktisch bei allem was mehr als ein Perl-Befehl ist.

Deswegen nutze ich einen Wrapper FW_cmd_perlcode() für FW_cmd() um mir das Leben leichter zu machen:
function FW_cmd_perlcode(arg, callback, rep) {
      let perlcode = "eval(MIME::Base64::decode_base64('" +btoa(arg)+ "'))";
      return FW_cmd(FW_root + '?cmd={' + perlcode + '}&XHR=1', callback, rep);
    }

Diese Funktion ist SEHR mächtig, man hat praktisch volle Rechte wie FHEM auch und den vollen Perl-Funktionsumfang. Das war aber auch immer schon im FHEM und auch immer schon ein Grund das Webinterface nicht ohne Authentifizierung zugänglich zu machen.

Als Demo ein SVG-Button FW_cmd_perlcode_demo.svg, der beim Anklicken einen Perl-Codeblock aus dem SVG ausführt:
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" style="width: 100px; height: 100px">
  <metadata class="perlcode"><![CDATA[
 
  Log(1,"Dies ist ein Test...");
  Log(1,"und noch einer...");
 
  my $bisselRechnen = 1 + 3 / 6.0;
  Log(1, "Habe was ausgerechnet: $bisselRechnen");
 
  ]]></metadata>

  <style>
    .button {
      fill: #ccc;
      stroke: #888;
      stroke-width: 2;
    }
    .button:hover {
      fill: #bbb;
    }
    .button:active {
      fill: #aaa;
    }
    .text {
      font-family: Arial, sans-serif;
      font-size: 12px;
      text-anchor: middle;
      fill: black;
      pointer-events: none;
    }
  </style>

  <script><![CDATA[
    let uuid = crypto.randomUUID();
    let script = document.currentScript;
    if (!script) {
      console.log('using fallback for currentScript');
      let scripts = document.querySelectorAll('svg > script:not([data-uuid])');
      if (scripts.length === 0) {
        console.warn('Could not find any matching scripts.');
      } else {
        script = scripts[scripts.length - 1];
      }
    }
    script?.setAttribute('data-uuid', uuid);
   
    /* sende Perlcode an das FHEM Backend */
    function FW_cmd_perlcode(arg, callback, rep) {
      let perlcode = "eval(MIME::Base64::decode_base64('" +btoa(arg)+ "'))";
      return FW_cmd(FW_root + '?cmd={' + perlcode + '}&XHR=1', callback, rep);
    }
   
    /* entferne in FHEMWEB alle Zellen dieser Zeile die nicht StatusIcon sind */
    let table = script?.closest("table");
    if (table) {
      let maxCols = 0;
      for (let i = 0; i < table.rows.length; i++) {
        maxCols = Math.max(maxCols, table.rows[i].cells.length);
      }
      [...script?.closest('tr')?.querySelectorAll("td:not([informId])")].forEach(td => td.remove());
      script?.closest('td')?.setAttribute('colspan', maxCols);
    }
   
    function onClick(evt) {
      let svg;
     
      if (!script) {
        console.log('Fallback for currentScript');
       
        svg = evt.target.closest('svg');
        if (!svg) {
          console.warn('no SVG-element found, leaving.');
          return;
        }
       
        script = svg.querySelector('svg > script[data-uuid="'+uuid+'"]');
        if (!script) {
          console.warn('no matching script tag found, leaving');
          return;
        }
      }
     
      svg = script.closest('svg');
      let perlcodeElement = svg.querySelector('metadata.perlcode');
     
      if (!perlcodeElement) {
        console.error('no perlcode element found, leaving');
        return;
      }
     
      let perlcode = perlcodeElement.textContent ? perlcodeElement.textContent.trim() : '';
      if (!perlcode) {
        console.warn('no perlcode found, leaving');
        return;
      }
     
      if (typeof FW_cmd === 'function' && typeof FW_root !== 'undefined') {
        FW_cmd_perlcode(perlcode, function(data) {
          FW_okDialog(data);
        });
      } else {
        console.warn('FW_cmd or FW_root not defined.');
      }
    }
  ]]></script>

  <g onclick="onClick(evt)">
    <rect class="button" x="20" y="30" width="60" height="30" rx="2" ry="2"/>
    <text class="text" x="50" y="50">Click me</text>
  </g>
</svg>

Dies wäre ein Demodevice dafür:
defmod buttonSVG dummy
attr buttonSVG devStateIcon .*:FW_cmd_perlcode_demo