ヤマハルーター・PoE監視デモサンプルをWOL用に改造

はじめに

ふとしたことからヤマハのPoE受電端末監視/自動再起動デモを知った。
WOL用に改造できそうで,環境(RTX1210+SWX2200-8PoE)も同じだったので安易に試験導入してみることにしたが,これがハマってしまった。

※ PoE (Power over Ethernet)はIEEE 802.3afおよび802.3atで定義されるイーサネットの規格でLANケーブルを通して、電力を供給する技術のこと。ネットワークカメラや無線LANアクセスポイントの電源をPoEスイッチからLANケーブルを通して電源供給できるのでLANケーブル工事のみで設置できる。

ダウンロードしたデモツールをルーター(RTX1210)に入れて,ルーター管理下のSWX2200-8PoEに接続するネットワークカメラを監視させるものだが,接続機器(ネットワークカメラ等)の情報は保存出来るが,読み込みに失敗する。保存した機器情報を読み込めないので毎回立ち上げ時に接続機器を登録しないと使えないことが判明。ブラウザなのか,何か環境が違うのか。自分の環境が何か影響している?

問題解析

保存データの読み込みには,jQueryによるAjax関数のgetを使用してJavaScriptに直接取り込んでいる。ログを見るとエラー404でファイルが無いと言っている。ちなみに保存書き込みはjQueryによるAjax関数のpostでルーター側のluaスクリプトを起動して,間接的な書き込み処理が正常に行われており,若干処理が異なっている。
 ※Ajaxは「Asynchronous JavaScript and XML」の略でサーバーへ非同期で行う通信のこと。「非同期通信」対語「同期通信」

カスタムguiの場合は,urlがリダイレクト処理され目的のフォルダーの先頭にcustomフォルダーが自動的に付加される。最初はこのあたりが原因かと思ったがどうも違うみたい。
postは正常処理されているのでgetに絞って解決策を探ってみる。
jQueryによるAjax通信が問題なのかと,別方法の基本的な「XMLHttpRequest()」によるAjax通信でルーターにアクセスしてみたが,これもエラー404となる。ルーター側で拒んでいるのか?

代替え解決策

server保存に伴うserver側設定

ルーターへのデーター保存はひとまずあきらめて,常時稼働のserverに保存することした。
serverへはdatabase.txtとlogfile.txtを保存する。
jQueryによるAjax通信でserver側のurlを指定したが,cors設定の問題でエラー
cors未設定なので,たしかにその通りの結果だ。
自分がアクセスしているホスト(今の場合はルーター)とは別の常時稼働serverに保存して読み取りするには,server側にクロス ドメイン リクエスト対策が必要。
CORS (Cross-Origin Resource Sharing)は上記制約を一部解除し、異なるオリジン間でリソースを共有するための仕組み。

オリジン: https://example.com:port
プロトコル(スキーム): https
ドメイン(ホスト): example.com
ポート: port 「80又は443等」
※ プロトコル、ドメイン、ポートが一致していれば同一オリジンであり,異なればアクセス制限の対象となる。

http.conf

serevr側にcors設定のため apache のhttpd.confを変更する。
httpd.conf のheaders_moduleを読み込み設定(下記アンコメント)

LoadModule headers_module modules/mod_headers.so
.htaccess


下記の.htaccessファイルをCORS許可するdatabase.txtのフォルダーに置く。
念のためベーシック認証もさせる。ようとしたが通常設定ではダメ。
クロスドメインのベーシック認証は追加設定が必要だが,ここでハマってしまった。
メインのアクセスの前にpreflight(プリフライト)と呼ばれるOPTIONメソッドのリクエストをサーバーに送って事前のアクセスチェックを行うが,これに対する返答をサーバーが本アクセスと同様の401の認証要求を返してしまい,ブラウザ側が本来の返答と異なるのでヘッダーを破棄してしまいエラーとなる。OPTIONメソッドリクエストにはサーバーからの返答を200のOKとして返して,BASIC認証をしないようにLimitExceptディレクティブでサーバー側を設定する。BASIC認証の401レスポンス時にはalwaysオプションを付けないとヘッダが付与されないらしい。
Basic認証には2通りの方法があり,認証情報をAuthorizationヘッダーに埋め込む方法とURLの中に埋め込むURLエンコーディングがある。
CORSでは前者のAuthorizationヘッダーで認証する。

# cors設定 BASIC認証
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Origin: http://ルーターのIPアドレス
Header always set Access-Control-Allow-Methods: GET,POST,OPTIONS,PUT,DELETE
Header always set Access-Control-Allow-Headers: Authorization,User-Agent,Keep-Alive,Content-Type,accept,origin
Header always set Access-Control-Allow-Credentials: true
Header always set Access-Control-Max-Age: 600
</IfModule>

# OPTIONSに対する反応
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
</IfModule>

# BASIC認証
<LimitExcept OPTIONS>
#<Limit GET POST PUT DELETE>
 AuthType Basic
 AuthName "Please enter your ID and password"
 AuthUserFile d:/www/private_html/example/.htpasswd
 AuthGroupFile /dev/null
 require valid-user
#</Limit>
</LimitExcept>
蛇足

① ベーシック認証しない場合はPHPに追加しても良いみたい。試していないが。
 .htaccessに設定するcors設定をデータ受信処理するPHPに設定しても良いがクレデンシャルを必要とするベーシック認証にはワイルドカードは使えない。

header("Access-Control-Allow-Origin: *");   /* phpに指定する場合 */

② 今回試していないが,Basic認証をURLの中に埋め込むURLエンコーディングだとプリフライトが飛ばないので,.htaccessのプリフライト対策が不要となる。しかしURLにユーザーidとパスワードが表示されることになるのでセキュリティ上好ましくはない。「https://user:pass@example.com/example/wol/database.txt」

Header always set Access-Control-Allow-Headers: Authorization   # 不要

# OPTIONSに対する反応          以下</IfModule>まで不要
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
</IfModule>                  # ここまで不要

# BASIC認証
<LimitExcept OPTIONS>           # 不要
#<Limit GET POST PUT DELETE>    # 元々不要
 AuthType Basic
 AuthName "Please enter your ID and password"
 AuthUserFile d:/www/private_html/example/.htpasswd
 AuthGroupFile /dev/null
 require valid-user
#</Limit>                      # 元々不要
</LimitExcept>                 # 不要
PHP (database.php)

受け取ったファイルをdatabase.txtに後方追加保存

<?php
  /* database.php */
  $fileName='database.txt';
  $data=$_POST['post_data'] . "\r\n";
  file_put_contents($fileName,$data,FILE_APPEND);
?>
PHP (dataclear.php)

「database.txt」のデータ消去用でデータ保存前に初期化して空フアイルとする。

<?php
  /* dataclear.php */
  $fileName='database.txt';
  file_put_contents($fileName,"");
?>
PHP (logfile.php)

受け取ったファイルをlogfile.txtに前方追加保存

<?php
  /* logfile.php */
  $fileName='logfile.txt';
  $logdata=$_POST['post_log'] . "\r\n" . file_get_contents($fileName);
  file_put_contents($fileName,$logdata); 
?>
PHP (logclear.php)

「logfile.txt」のデータ消去用で「ログのクリア」で初期化して空フアイルとする。

<?php
  /* logclear.php */
  $fileName='logfile.txt';
  file_put_contents($fileName,"");
?>

JavaScript (monitor.js)改造

ダウンロードしたデモツールの「monitor.js」を変更してserverにデータ保存するように改造する。
保存するデータはデフォルトではmacアドレスを含んでいないが,wol用に使用する為,macアドレスも保存する。
serverへのjQueryのAjax関数GET通信時は404エラーとなるので,「XMLHttpRequest()」によるAjax通信を使用。逆にPOSTはjQueryのAjax関数を使用。何だかハチャメチャのコードだがとりあえず動くのでエラー原因は不明だが時間があれば調べたい。とりあえず動く方法で行く。
PoEの処理コードはコメントで残しつつWOLのコードを追加していった。

monitor.js 主な追加変更箇所説明

初期値設定(パラメーター定義)

いろいろ設定変更を加えながらテストを行ったのでその名残でそのままにしてある。

var Protocol = "https://";   // "http://" | "https://" 
var Host = "example.com";    // データ保存サーバードメイン | IPアドレス | ルーターIP
var Port = "";    // Port Number  "" | ":80" | "443"
var Path = "/example/wol/";     // データ保存フォルダーパス
var authuser = "user";         // ベーシック認証 ユーザーid
var authpass = "pass";         // ベーシック認証 パスワード
var authstr = 'Basic ' + window.btoa(authuser + ':' + authpass);  // Authorizationヘッダー埋込認証用
// NOTE: 'Basic 'の後↑に半角スペースが必要
database.txtのget処理追加変更分

jQueryによるAjax関数のgetではエラー404となるので,JavaScriptの「XMLHttpRequest()」でテストしたら上手くいった。設定がなにか違うのか。

   dataurl = Protocol + Host + Port + Path + "database.txt"
   var xhr = new XMLHttpRequest();

// URLエンコーディング 認証 URLに直接表示されるので,セキュリティ上指定しないほうが良い
//   xhr.open("GET", dataurl , true , authuser , authpass);

// 上記のURL認証情報を省いて指定 trueはデフォルト値の非同期設定,同期はfalse
   xhr.open("GET", dataurl);

// Authorizationヘッダー 認証  Authorizationヘッダーに埋め込む
   xhr.setRequestHeader("Authorization" , authstr);

// CORSでBasic認証リクエスト時はクレデンシャルをtrueに指定する
   xhr.withCredentials = true;

// リクエストの送信
   xhr.send();

// 正常読み込み終了で次のステップへ
   xhr.onreadystatechange = function() {
      if(xhr.readyState === 4 && xhr.status === 200) {
        var data = xhr.responseText;

//  オリジナルのget処理なのでコメント 当方の環境では404エラーとなって読み込みできない。 
//    getResource("/custom/poe/database.txt", function(data) {    // コメント
MACアドレス保存追加

保存データ3項目(IPアドレス,コメント,復旧方法)から4項目(IPアドレス,MACアドレス,コメント,復旧方法)に変更。

            // 端末の登録
            for (i = 0; i < str.length; i++) {
                line = str[i].split(",");
                if (line.length != 4)       // 変更
                    continue;
                rows = tlist.insertRow(-1);
                rows.style.height = "40";
                num = tlist.rows.length;
                for (j = 0; j < 8; j++) {
                    rows.insertCell(-1);
                }
                rows.cells[0].innerHTML = String(i + 1);
                rows.cells[1].innerHTML = line[0];
                rows.cells[2].innerHTML = line[1];      // 変更
                rows.cells[3].innerHTML = "";
                rows.cells[4].innerHTML = line[2];      // 変更
                rows.cells[5].innerHTML = "確認中";
                if (line[3].indexOf("auto") != -1) {    // 変更
                    rows.cells[6].innerHTML = "自動復旧";
                } else {
                    rows.cells[6].innerHTML = "手動復旧  <input type=\"button\" value=\"実行\" onclick=\"offPortUse(\'" + line[0] + "\', 1);\">";
                }
                rows.cells[7].innerHTML = "<input type=\"button\" value=\"削除\" onclick=\"deleteTarget(\'" + line[0] + "\');\">";
                rows.style.textAlign = "center";
     :
   この間省略
         :
        }
        updateStatus();
      }                            // 追加
   };                              // 追加
});
     :
   この間省略
         :
//
// 現状の監視対象のリストを保存する
//
function saveTargetList() {
   var i, str;
   var rows, line = "", total = "";
   var cmd = "lua /poe/save.lua ";
   DataClear();                                    // 追加
   total = String(tlist.rows.length - 1) + " ";
   for (i = 1; i < tlist.rows.length; i++) {
       rows = tlist.rows[i];
       if (isValidIPaddress(rows.cells[1].innerHTML, 0))
           continue;
       line = rows.cells[1].innerHTML + "," + rows.cells[2].innerHTML + "," + rows.cells[4].innerHTML + ",";       // 変更
       str = rows.cells[6].innerHTML.split(" ");
       if (str[0] == "自動復旧")
           line = line + "auto ";
       else
           line = line + "manual ";
       total = total + line;
       DataSave(line);                             // 追加
   }
   cmd = cmd + total;
//   cmdExecute(cmd);      // コメント
}
logfile.txtのget処理追加変更分

database.txtのgetと同様理由で処理変更

$(document).ready(function() {
   logurl = Protocol + Host + Port + Path + "logfile.txt"
   var xhr = new XMLHttpRequest();
   xhr.open("GET", logurl);
   xhr.setRequestHeader("Authorization" , authstr);
   xhr.withCredentials = true;
   xhr.send();
   xhr.onreadystatechange = function() {
      if(xhr.readyState === 4 && xhr.status === 200) {
        var data = xhr.responseText;
//    getResource("/custom/poe/logfile.txt", function(data) {  //コメント
               :
               : この間省略
               :
                    log.innerHTML = log.innerHTML + "<br>" + str[i];
                }
            }
        }
      }        // 追加
   };          // 変更  }); → }; 
});
WOLコマンド送信処理

PoEコマンドに代えてWOLコマンド送信に変更。PoEコマンドはコメントで残す。

    for (i = 1; i < tlist.rows.length; i++) {
        if (tlist.rows[i].cells[1].innerHTML == target) {
            port = tlist.rows[i].cells[3].innerHTML;
            mac = tlist.rows[i].cells[2].innerHTML;      // 追加 WOL用
            break;
        }
    }
// 追加 ########### WOL ################################# 開始
    if (mac == "") {
        return;
    }
    cmd0 = "wol send lan1 " + mac;
    cmdExecute(cmd0);
// 追加 ########### WOL ################################# 終了

//    if (port == "") {                // コメント
//        return;
//    }

//    cmdExecute(cmd1);              // PoEコマンド 以下コメント
//    cmd2 = "switch control function set poe-class " + port + " none";
//    cmdExecute(cmd2, setPortUse, port, target, manual);
    rec_ele.value = 1;
    tlist.rows[i].cells[5].innerHTML = "WOL起動中";     // 名称をWOLに変更
database.txtのpost処理追加変更分

どちらも試してみたが,JavaScriptの「XMLHttpRequest()」は設定が悪いのか,受信側のPHP処理で受け取れない。LOGは一部しか受け取れなかった。jQueryによるAjax関数で上手くいったのでそちらを利用。たぶんPHPの配列(Array)処理の設定かな。
データ保存前にデータ消去処理を1回実施する。その後,登録機器1レコード毎にPOST処理でサーバーに送信する。

//
// データベースを保存する
//
function DataSave(data) {
dataurl = Protocol + Host + Port + Path + "database.php"

  $(function(){
//  jQuery(function($){
    //ajax送信
    $.ajax({
        url : dataurl,
        type : "POST",
        dataType : 'json',
        data : {post_data:data},
// URLエンコーディング 認証
//        username: authuser,
//        password: authpass,
// Authorizationヘッダー 認証
        headers: {
         "Authorization": "Basic " + btoa(authuser + ":" + authpass)
        },
        xhrFields: {
          withCredentials: true
        }
    });
  });
}
//
// データベースを消去する
//
function DataClear() {
  datacurl = Protocol + Host + Port + Path + "dataclear.php"
  var xhr = new XMLHttpRequest();
  xhr.open('POST', datacurl);
  xhr.setRequestHeader("Authorization" , authstr);
  xhr.withCredentials = true;
  xhr.send();
}
logfile.txtのpost処理追加変更分

新規ログ発生毎に各セクション(2か所)からLogSaveを呼び出して,サーバーにPOST処理する。
database.txtのpost処理と同様理由で変更
ログファイル消去処理は消去ボタン押下時に実行

//
// ログファイルを保存する
//
function LogSave(log) {
logurl = Protocol + Host + Port + Path + "logfile.php"

  $(function(){
    //ajax送信
    $.ajax({
        url : logurl,
        type : 'POST',
        dataType : 'json',
        data : {post_log:log},
        headers: {
         'Authorization': 'Basic ' + btoa(authuser + ':' + authpass)
        },
        xhrFields: {
          withCredentials: true
        }
    });
  });
}
//
//
// ログファイルを消去する
//
function LogClear() {
  logcurl = Protocol + Host + Port + Path + "logclear.php"
  var xhr = new XMLHttpRequest();
  xhr.open('POST', logcurl);
  xhr.setRequestHeader("Authorization" , authstr);
  xhr.withCredentials = true;
  xhr.send();
}

monitor.js 変更後

網掛けは追加・変更箇所

var poeRoute = "";   // 操作対象PoEスイッチの経路情報
var tlist;           // 監視対象一覧のテーブルのオブジェクト
var clist;           // Ping失敗数カウント用テーブルのオブジェクト
var logfd;           // ログ表示用フィールドのオブジェクト
var intobj;          // Ping定期実行用インターバルのオブジェクト
var filobj;          // フィルター状態のオブジェクト

// 以下パラメーター定義
var Protocol = "https://";   // "http://" | "https://" 
var Host = "example.com";    // データ保存サーバードメイン | IPアドレス | ルーターIP
var Port = "";    // Port Number  "" | ":80" | "443"
var Path = "/example/wol/";     // データー保存フォルダーパス
var authuser = "user";         // ベーシック認証 ユーザーid
var authpass = "pass";         // ベーシック認証 パスワード
var authstr = 'Basic ' + window.btoa(authuser + ':' + authpass);  // Authorizationヘッダー埋込認証用
// NOTE: 'Basic 'の後↑に半角スペースが必要
var maxEntryNum = 16;               // 登録可能最大数
var pingWaitInterval = 1;          // Pingの待ち時間(s)
var pingExecInterval = 3000;       // Pingの一斉実行間隔(ms)
var pingErrorCount = 3;            // 何回Pingに失敗したらダウンと判断するか
var poeRecoverInterval = 5000;     // PoE給電を止めてから再供給までの時間(ms)
var filtersetInterval = 15000;     // ICMPのrejectフィルタの適用時間

function getResource(url, callback) {
    $.ajax({
        type: "get",
        url: url,
        contentType: "charset=shift_jis",
        cache: false,
        success: function(data) {
            callback(data);
        }
    });
}

function cmdExecute(cmd, callback, arg1, arg2, arg3) {
    $.ajax({
        type: "post",
        url: "/custom/execute",
        cache: false,
        data: "#" + getSessionId() + "\r\n" + cmd,
        success: function(data) {
            if (callback != undefined)
                callback(data, arg1, arg2, arg3);
        }
    });
}

//
// 引数がIPアドレスとして正しいものか調べる
//
function isValidIPaddress(target, on) {
    var i;
    var confirm = document.getElementById('confirm');
    var factor;

    if (target.indexOf(".0") != -1) {
        if (on)
            confirm.innerHTML = "<font color=\"red\">入力値に誤りがあります</font>";
        return 1;
    }
    factor = target.split('.');
    if (factor.length != 4) {
        if (on)
            confirm.innerHTML = "<font color=\"red\">入力値に誤りがあります</font>";
        return 1;
    }
    for (i = 0; i < 4; i++) {
        if (factor[i].match(/[^0-9]+/)) {
            if (on)
                confirm.innerHTML = "<font color=\"red\">入力値に誤りがあります</font>";
            return 1;
        }
    }
    for (i = 0; i < target.length; i++) {
        if (encodeURI(target.charAt(i)).length >= 4) {
            if (on)
                confirm.innerHTML = "<font color=\"red\">入力値に誤りがあります</font>";
            return 1;
        }
    }
    for (i = 0; i < 4; i++) {
        var num = Number(factor[i]);
        if (num == NaN || factor[i] < 0 || factor[i] > 255) {
            if (on)
                confirm.innerHTML = "<font color=\"red\">入力値に誤りがあります</font>";
            return 1;
        } 
    }

    confirm.innerHTML = " ";
    return 0;
}

//
// 入力されたIPアドレスからMACアドレスを調べる
//
function getMacAddress() {
    var cmd = "show arp lan1";
    cmdExecute(cmd, showMacAddress);
}

$(document).ready(function() {

    tlist = document.getElementById('target_list');
    clist = document.getElementById('target_list_cnt');
    logfd = document.getElementById('log');
    filobj = document.getElementById('filter_status');

    // PoEスイッチの経路を取得
    cmdExecute("show status switch control lan1", getPoeRoute);

    // コマンドの実行で管理者へ
    cmdExecute("no syslog info", updateStatusMain);

    // データベースを開く
// 追加変更 ##################################################### 開始
   dataurl = Protocol + Host + Port + Path + "database.txt"
   var xhr = new XMLHttpRequest();
   xhr.open("GET", dataurl);
   xhr.setRequestHeader("Authorization" , authstr);
   xhr.withCredentials = true;
   xhr.send();
   xhr.onreadystatechange = function() {
      if(xhr.readyState === 4 && xhr.status === 200) {
        var data = xhr.responseText;
//    getResource("/custom/poe/database.txt", function(data) {    // コメント
// 追加変更 ##################################################### 終了
        var i, j, str, rows, line, num, cnt, rec;
        if (data != undefined) {
            str = data.split("\n");
            // 端末の登録
            for (i = 0; i < str.length; i++) {
                line = str[i].split(",");
                if (line.length != 4)      // 変更
                    continue;
                rows = tlist.insertRow(-1);
                rows.style.height = "40";
                num = tlist.rows.length;
                for (j = 0; j < 8; j++) {
                    rows.insertCell(-1);
                }
                rows.cells[0].innerHTML = String(i + 1);
                rows.cells[1].innerHTML = line[0];
                rows.cells[2].innerHTML = line[1];      // 変更
                rows.cells[3].innerHTML = "";
                rows.cells[4].innerHTML = line[2];      // 変更
                rows.cells[5].innerHTML = "確認中";
                if (line[3].indexOf("auto") != -1) {    // 変更
                    rows.cells[6].innerHTML = "自動起動";
                } else {
                    rows.cells[6].innerHTML = "手動起動  <input type=\"button\" value=\"実行\" onclick=\"offPortUse(\'" + line[0] + "\', 1);\">";
                }
                rows.cells[7].innerHTML = "<input type=\"button\" value=\"削除\" onclick=\"deleteTarget(\'" + line[0] + "\');\">";
                rows.style.textAlign = "center";
                
                // 端末毎のPing欠落回数の設定
                rows = clist.insertRow(-1);
                num = clist.rows.length;
                cnt = "cnt_" + line[0];
                rec = "rec_" + line[0];
                rows.insertCell(-1);
                rows.insertCell(-1);
                rows.cells[0].innerHTML = "<input type=\"hidden\" id=\"" + cnt + "\">";
                rows.cells[1].innerHTML = "<input type=\"hidden\" id=\"" + rec + "\">";
                document.getElementById(cnt).value = 0;
                document.getElementById(rec).value = 0;
            }
            // MACアドレスと経路情報の取得と表示
            setTimeout(getMacAddress, 1000);
        }
        updateStatus();
      }                            // 追加
   };                              // 追加
});     // $(document).ready(function() {

    // ログファイルを開く
// 追加変更 ##################################################### 開始
$(document).ready(function() {
   logurl = Protocol + Host + Port + Path + "logfile.txt"
   var xhr = new XMLHttpRequest();
   xhr.open("GET", logurl);
   xhr.setRequestHeader("Authorization" , authstr);
   xhr.withCredentials = true;
   xhr.send();
   xhr.onreadystatechange = function() {
      if(xhr.readyState === 4 && xhr.status === 200) {
        var data = xhr.responseText;
//    getResource("/custom/poe/logfile.txt", function(data) {  //コメント
// 追加 ##################################################### 終了
        if (data != undefined) {
            var log = document.getElementById('log');
            str = data.split("\r\n");
            for (i = 0; i < str.length; i++) {
                if (i == 0) {
                    log.innerHTML = str[i];
                } else {
                    log.innerHTML = log.innerHTML + "<br>" + str[i];
                }
            }
        }
      }  // if(xhr.readyState === 4 && xhr.status === 200) {
   };   // xhr.onreadystatechange = function() {
});     // $(document).ready(function() {

// ##################################
//
// PoEスイッチの経路情報を取得する
// show status switch controlコマンドの出力で最初に出てきたPoEスイッチが対象
//
function getPoeRoute(data) {
    var i, flag = 0;
    var str1 = data.split("\r\n");
    var str2;
    var rte = "";
    var poe = document.getElementById('target_poe');

    for (i = 0; i < str1.length; i++) {
        if (str1[i].indexOf("SWX2200-8PoE") != -1)
            flag = 1;
        if (str1[i].indexOf("設定用経路") != -1 && flag) {
            str2 = str1[i].replace(/\s{2,}/g, ' ');
            rte = str2.split(" ");
            poeRoute = rte[2];
            poe.innerHTML = "操作対象PoEスイッチ:" + poeRoute;
            return;
        }
    }
    poe.innerHTML = "操作対象PoEスイッチ:<font color=\"red\">みつかりません</font>";
}

function updateStatusMain(data) {
    updateStatus();
    setInterval(updateStatus, pingExecInterval);
}

//
// Pingによる疎通確認を行う
//
function updateStatus() {
    // 対象が無ければ何もしない
    if (tlist.rows.length > 1) {
        var i;
        for (i = 1; i < tlist.rows.length; i++){
            updateStatusEach(i)
        }
        intobj = setTimeout(function() {checkPing();}, 2000);
    }
}

//
// テーブルのNoを指定してPingによる疎通確認を行う
//
function updateStatusEach(id) {
    var target;
    var cmd = "lua /poe/ping.lua " + pingWaitInterval;

    if (tlist.rows.length == id)
        return;

    target = tlist.rows[id].cells[1].innerHTML;
    if (isValidIPaddress(target, 0))
        return;
    cmd = cmd + " " + target;
    cmdExecute(cmd);
}

//
// Pingの結果を確認するため、show status luaを実行する
//
function checkPing() {
	var cmd = "show status lua history";

	cmdExecute(cmd, checkLuaStatus);
}

//
// Pingの結果を表示に反映させる
// ダウン時の自動起動を実行する
//
function checkLuaStatus(data) {
    var i, j, id, flag;
    var str = data.split("\r\n");
    var str2;
    var target;
    var cel2, cel5, cel6;
    var cnt, rect, cnt_ele, rec_ele, cnt_num, rec_num;

    for (i = 1; i < tlist.rows.length; i++) {
        target = tlist.rows[i].cells[1].innerHTML;
        cel2 = tlist.rows[i].cells[2];
        cel5 = tlist.rows[i].cells[5];
        cel6 = tlist.rows[i].cells[6];
        cnt = "cnt_" + target;
        rec = "rec_" + target;
        cnt_ele = document.getElementById(cnt);
        rec_ele = document.getElementById(rec);
        cnt_num = Number(cnt_ele.value);
        rec_num = Number(rec_ele.value);
        flag = 0;

        for (j = 0; j < str.length; j++) {
            str2 = str[j].replace(/\s{2,}/g, ' ');
            str2 = str2.split(' ');
            if (str2[4] == target) {
                flag = 1;
                continue;
            }
            if (flag == 0) {
                continue;
            }
            if (str[j].indexOf("前回の走行結果:") != -1) {
                if (str[j].indexOf("正常終了") != -1) {
                    if (cel5.innerHTML == "OFF" || cel5.innerHTML.indexOf("確認中") != -1 || cel5.innerHTML.indexOf("WOL起動中") != -1 ) {   // 変更
                        makeStateLog(target, 1);
                    }
                    cel5.innerHTML = "ON";
                    cel5.style.backgroundColor = "#7bdf2e";
                    cnt_ele.value = 0;
                    rec_ele.value = 0;
                    // MACアドレスの更新
                    if (cel2.innerHTML == "") {
                        getMacAddress();
                    }
                } else {
                    if (cnt_num < pingErrorCount) {
                        cnt_num += 1;
                    }
                    cnt_ele.value = cnt_num;
                    if (rec_num > 0) {
                        break;
                    }
                    if (cnt_num >= pingErrorCount) {
                        if (cel5.innerHTML == "ON" || cel5.innerHTML.indexOf("確認中") != -1) {
                            makeStateLog(target, 0);
                        }
                        cel5.innerHTML = "OFF";
                        cel5.style.backgroundColor = "#F83131";
                        if (rec_num == "0" && cel6.innerHTML == "自動起動") {
                            // 自動起動
                            offPortUse(target, 0);
                        }
                    }
                }
                break;
            }
        }
    }
}

//
// 指定したNoの接続ポートの給電をOFFにする
//
function offPortUse(target, manual) {
    var i;
    var cmd1 = "switch select " + poeRoute;
    var cmd2;
    var rec = "rec_" + target;
    var rec_ele = document.getElementById(rec);
    var port;

    for (i = 1; i < tlist.rows.length; i++) {
        if (tlist.rows[i].cells[1].innerHTML == target) {
            port = tlist.rows[i].cells[3].innerHTML;
            mac = tlist.rows[i].cells[2].innerHTML;      // 追加 WOL用
            break;
        }
    }

// 追加 ########### WOL ################################# 開始
    if (mac == "") {
        return;
    }
    cmd0 = "wol send lan1 " + mac;
    cmdExecute(cmd0);
// 追加 ########### WOL ################################# 終了

//    if (port == "") {       // コメント
//        return;             // コメント
//    }                       // コメント

//    cmdExecute(cmd1);              // PoEコマンド  コメント
//    cmd2 = "switch control function set poe-class " + port + " none";
//    cmdExecute(cmd2, setPortUse, port, target, manual);
    rec_ele.value = 1;
    tlist.rows[i].cells[5].innerHTML = "WOL起動中";         // 変更
    tlist.rows[i].cells[5].style.backgroundColor = "#FF9900";
    makePoeLog(manual, target, 0);

    // Pingの監視をリスタート
    clearTimeout(intobj);
    setTimeout(updateStatus, 3000);
}

//
// 給電をONにするためのタイマーをセットする
//
function setPortUse(data, port, target, manual) {
    setTimeout(function(port, target, manual) {
        var cmd1 = "switch select " + poeRoute;
        var cmd2 = "no switch control function set poe-class " + port;

//        cmdExecute(cmd1);                    // コメント
//        cmdExecute(cmd2);                    // コメント
        makePoeLog(manual, target, 1);},
        poeRecoverInterval, port, target, manual);
}

//
// 給電設定変更のログを作る
//
function makePoeLog(manual, target, on) {
    var dateinfo = new Date();
    var year = dateinfo.getFullYear();
    var month = dateinfo.getMonth() + 1;
    var day = dateinfo.getDate();
    var hour = dateinfo.getHours();
    var minute = dateinfo.getMinutes();
    var second = dateinfo.getSeconds();
    var time;
    var method = (manual == 1) ? "[手動起動]" : "[自動起動]";   // 変更
    var proc = (on == 1) ? "の給電を再開しました" : "へマジックパケットを送信しました";   // 変更
    var str;

    month = doubleNumber(month);
    day = doubleNumber(day);
    hour = doubleNumber(hour);
    minute = doubleNumber(minute);
    second = doubleNumber(second);
    time = year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
    str = time + " " + method + target + proc;
    logfd.innerHTML = str + "<br>" + logfd.innerHTML;
//    cmdExecute("lua /poe/log.lua \"" + str + "\"");   // コメント
    LogSave(str);                                       // 追加 log保存
}

//
// 監視状態変更のログを作る
//
function makeStateLog(target, on) {
    var dateinfo = new Date();
    var year = dateinfo.getFullYear();
    var month = dateinfo.getMonth() + 1;
    var day = dateinfo.getDate();
    var hour = dateinfo.getHours();
    var minute = dateinfo.getMinutes();
    var second = dateinfo.getSeconds();
    var time;
    var proc = (on == 1) ? "の状態が [<b><font color=\"green\">ON</font></b>] になりました" : "の状態が [<b><font color=\"red\">OFF</font></b>] になりました";
    var str;

    month = doubleNumber(month);
    day = doubleNumber(day);
    hour = doubleNumber(hour);
    minute = doubleNumber(minute);
    second = doubleNumber(second);
    time = year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
    str = time + " " + target + proc;
    logfd.innerHTML = str + "<br>" + logfd.innerHTML;
//    cmdExecute("lua /poe/log.lua \"" + str + "\"");
    LogSave(str);                                       // 追加 log保存
}

//
// 1桁の数字を2桁にする(1->01)
//
function doubleNumber(num) {
    num += "";
    if (num.length === 1) {
        num = "0" + num;
    }
    return num;
}

//
// 端末の登録を行う
//
function registDevice1() {
    var i, num, ret;
    var target = document.forms.id_target_form.elements.target.value;
    var comment = document.forms.id_target_form.elements.comment.value;
    var confirm = document.getElementById('confirm');
    var cmd;

    // IPアドレス入力チェック
    if (isValidIPaddress(target, 1))
        return;

    // コメント入力チェック
    if (comment.indexOf(',') != -1) {
        confirm.innerHTML = "<font color=\"red\">コメントに , は使用できません</font>";
        return;
    }

    // 設定数上限の確認
    if (tlist.rows.length == maxEntryNum + 1) {
        confirm.innerHTML = "<font color=\"red\">これ以上登録できません</font>";
        return;
    }

    // 管理者への昇格およびARPテーブルの更新
    cmd = "lua /poe/ping.lua 1 " + target;
    cmdExecute(cmd);
    setTimeout(registDevice2, 1000);
}

function registDevice2() {
    var i, num;
    var ret = 0;
    var target = document.forms.id_target_form.elements.target.value;
    var comment = document.forms.id_target_form.elements.comment.value;
    var recover = document.forms.id_target_form.id_recover1.checked;
    var rows, cnt, rec, cmd;

    // 登録情報の上書き
    for (i = 1; i < tlist.rows.length; i++) {
        rows = tlist.rows[i];
        if (rows.cells[1].innerHTML == target) {
            rows.cells[4].innerHTML = comment;
            rows.cells[5].innerHTML = "確認中";
            if (recover == true)
                rows.cells[6].innerHTML = "自動起動";
            else
                rows.cells[6].innerHTML = "手動起動  <input type=\"button\" value=\"実行\" onclick=\"offPortUse(\'" + target + "\', 1);\">";
            return;
        }
    }

    // 端末の登録
    rows = tlist.insertRow(-1);
    rows.style.height = "40";
    num = tlist.rows.length;
    for (i = 0; i < 8; i++) {
        rows.insertCell(-1);
    }
    rows.cells[0].innerHTML = String(num - 1);
    rows.cells[1].innerHTML = target;
    rows.cells[2].innerHTML = "";
    rows.cells[3].innerHTML = "";
    rows.cells[4].innerHTML = comment;
    rows.cells[5].innerHTML = "確認中";
    if (recover == true)
        rows.cells[6].innerHTML = "自動起動";
    else
        rows.cells[6].innerHTML = "手動起動  <input type=\"button\" value=\"実行\" onclick=\"offPortUse(\'" + target + "\', 1);\">";
    rows.cells[7].innerHTML = "<input type=\"button\" value=\"削除\" onclick=\"deleteTarget(\'" + target + "\');\">";
    rows.style.textAlign = "center";


    // 端末毎のPing欠落回数の設定
    rows = clist.insertRow(-1);
    num = clist.rows.length;
    cnt = "cnt_" + target;
    rec = "rec_" + target;
    rows.insertCell(-1);
    rows.insertCell(-1);
    rows.cells[0].innerHTML = "<input type=\"hidden\" id=\"" + cnt + "\">";
    rows.cells[1].innerHTML = "<input type=\"hidden\" id=\"" + rec + "\">";
    document.getElementById(cnt).value = 0;
    document.getElementById(rec).value = 0;

    // MACアドレスと経路情報の取得と表示
    setTimeout(getMacAddress, 1000);

    return;
}

//
// 導出したMACアドレスを表示する
//
function showMacAddress(data) {
    var i, j;
    var str1 = data.split("\r\n");
    var str2;
    var target_ip;
    var macaddr;

    for (i = 1; i < tlist.rows.length; i++) {
        target_ip = tlist.rows[i].cells[1].innerHTML;
        for (j = 0; j < str1.length; j++) {
            str2 = str1[j].replace(/\s{2,}/g, ' ');
            str2 = str2.split(' ');
            if (str2[1] == target_ip) {
                tlist.rows[i].cells[2].innerHTML = str2[2];
                getPoePort(str2[2], i);
            }
        }
    }
}

//
// MACアドレスからPoEスイッチのどのポートに接続されているか調べる
//
function getPoePort(macaddr, i) {
    var cmd = "switch control function get status-macaddress-addr " + macaddr + " " + poeRoute;
    cmdExecute(cmd, showPoePort, i);
}

//
// 導出したポート情報を表示する
//
function showPoePort(data, i) {
    var str;

    if (data == undefined)
        return;
    str = data.split("\r\n");
    tlist.rows[i].cells[3].innerHTML = str[0];
}

//
// 現状の監視対象のリストを保存する
//
function saveTargetList() {
   var i, str;
   var rows, line = "", total = "";
   var cmd = "lua /poe/save.lua ";
   DataClear();                       // 追加
   total = String(tlist.rows.length - 1) + " ";
   for (i = 1; i < tlist.rows.length; i++) {
       rows = tlist.rows[i];
       if (isValidIPaddress(rows.cells[1].innerHTML, 0))
           continue;
       line = rows.cells[1].innerHTML + "," + rows.cells[2].innerHTML + "," + rows.cells[4].innerHTML + ",";      // 変更
       str = rows.cells[6].innerHTML.split(" ");
       if (str[0] == "自動起動")
           line = line + "auto ";
       else
           line = line + "manual ";
       total = total + line;
       DataSave(line);                             // 追加
   }
   cmd = cmd + total;
//   cmdExecute(cmd);                            // コメント
}

// 追加 ##################################################### 開始
//
// データベースを保存する
//
function DataSave(data) {
dataurl = Protocol + Host + Port + Path + "database.php"

  $(function(){
//  jQuery(function($){
    //ajax送信
    $.ajax({
        url : dataurl,
        type : "POST",
        dataType : 'json',
        data : {post_data:data},
        headers: {
         "Authorization": "Basic " + btoa(authuser + ":" + authpass)
        },
        xhrFields: {
          withCredentials: true
        }
    });
  });
}
//
// データベースを消去する
//
function DataClear() {
  datacurl = Protocol + Host + Port + Path + "dataclear.php"
  var xhr = new XMLHttpRequest();
  xhr.open('POST', datacurl);
  xhr.setRequestHeader("Authorization" , authstr);
  xhr.withCredentials = true;
  xhr.send();
}
// 追加 ##################################################### 終了
//
// 監視対象一覧から1行削除する
//
function deleteTarget(target) {
    var i, rows;

    // tlistから削除
    for (i = 1; i < tlist.rows.length; i++) {
        if (tlist.rows[i].cells[1].innerHTML == target) {
            tlist.deleteRow(i);
            break;
        }
    }
    for (i = 1; i < tlist.rows.length; i++) {
        tlist.rows[i].cells[0].innerHTML = i;
    }

    // clistから削除
    for (i = 0; i < clist.rows.length; i++) {
        if (clist.rows[i].cells[0].innerHTML.indexOf("id=\"cnt_" + target + "\"") != -1) {
            clist.deleteRow(i);
            break;
        }
    }
}

// 追加変更 ##################################################### 開始
//
// ログファイルを保存する
//
function LogSave(log) {
logurl = Protocol + Host + Port + Path + "logfile.php"

  $(function(){
    //ajax送信
    $.ajax({
        url : logurl,
        type : 'POST',
        dataType : 'json',
        data : {post_log:log},
        headers: {
         'Authorization': 'Basic ' + btoa(authuser + ':' + authpass)
        },
        xhrFields: {
          withCredentials: true
        }
    });
  });
}
//
//
// ログファイルを消去する
//
function LogClear() {
  logcurl = Protocol + Host + Port + Path + "logclear.php"
  var xhr = new XMLHttpRequest();
  xhr.open('POST', logcurl);
  xhr.setRequestHeader("Authorization" , authstr);
  xhr.withCredentials = true;
  xhr.send();
}
// 追加変更 ##################################################### 終了
//
// ログファイルの削除とログフィールドのクリア
//
function clearLog() {
    var cmd = "lua /poe/deletelog.lua";
//    cmdExecute(cmd);     // コメント
    logfd.innerHTML = "";
    LogClear();       // 追加
}

//
// 遮断用フィルタの設定
//
function setRejectFilter() {
    var cmd;
    cmd = "ip filter 1 reject * * icmp * *\n";
    cmd += "ip filter 100 pass * * * * *\n";
    cmd += "ip lan1 secure filter in 1 100\n";
    cmdExecute(cmd);
    filter_status.innerHTML = "<font color=\"red\">通信を遮断中</font>";
    setTimeout(resetRejectFilter, filtersetInterval);
}

//
// 遮断用フィルタの解除
//
function resetRejectFilter() {
    var cmd;
    cmd = "no ip filter 1\n";
    cmd += "no ip filter 100\n";
    cmd += "no ip lan1 secure filter in\n";
    cmdExecute(cmd);
    filter_status.innerHTML = "";
}

index.html変更

このツールはデモ用で,復旧動作確認用の通信遮断ボタンが表示されている。これはcofigの内容を書き換えてしまい,不要なのでコメント非表示とする。
jQueryのプログラム一式はダウンロードからCDN利用に変更。
デフオルト復旧方法を手動に変更。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=shift_jis">
<!--    <script type="text/javascript" src="jquery-1.11.1.min.js"></script> -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
    <script type="text/javascript" src="/custom/custom_gui_lib.js"></script>
    <script type="text/javascript" src="monitor.js"></script>
    <script type="text/javascript">
    function logout() {
      window.location.href = "logout.html";
    }
    </script>
    <link href="custom.css" rel="stylesheet" type="text/css"/>
<title>ネットワーク機器監視システム</title>
</head>

<body>

<header>
    <div class="header">
        <input id="logout" type="button" class="logout_button" value="ログアウト" onclick="logout();">
        <img src="logo.png" alt="yamaha logo">
     </div>
    <div class="h3">
       <h3>WOLマジックパケット送信用<br>
           YAMAHAのPoE監視デモツールを改造したため<br>
          表示等は合わない事があります。</h3>
    </div>
</header>

<!--監視対象の登録-->
<div id="register_field">
<img class="register_title_icon" src="register_icon.png" alt=""><h1 class="register_title">監視対象の登録</h1>
<form name="target_form" id="id_target_form" action="">
    <table border="1">
        <tbody>
            <tr>
                <td class="td">監視対象IPアドレス</td>
                <td><input type="text" name="target" id="id_target" maxlength="15"></td>
            </tr>
            <tr>
            <td class="td">コメント</td>
               <td><input type="text" name="comment" id="id_comment" maxlength="30"></td>
            </tr>
                <tr>
                <td class="td">起動方法</td>
                <td><input type="radio" name="recover" id="id_recover1" value="auto">自動起動<br>
                   <input type="radio" name="recover" id="id_recover2" value="manual" checked="">手動起動</td>
           </tr>
      </tbody>
   </table>
   <div id="confirm"> </div>
   <div align="right"><input type="button" class="normal_button" value="登  録" onclick="registDevice1();"></div>
</form>
</div>

<!--起動状況ログ-->
<div id="log_field">
    <img class="log_title_icon" src="log.png" alt=""><h1 class="log_title">起動状況ログ</h1>
    <div id="log" class="log"></div>
    <div> </div>
    <div align="right"><input type="button" class="normal_button" value="ログのクリア" onclick="clearLog();"></div>
</div>

<hr>

<div id="list_field">
<div id="list_field_title">
    <img class="list_title_icon" src="camera.png" alt=""><h1 class="list_title">監視対象一覧</h1>
</div>
<div class="target_poe" id="target_poe"></div>
<div align="right" style="padding-bottom: 10px; margin-top: -20px;">
    <input type="button" class="normal_button" value="監視対象情報を保存" onclick="saveTargetList();"/>
</div>
<table border="1" id="target_list" width="100%"><tbody>
    <tr class="tr">
        <th width="10">No.</th>
        <th width="130">IPアドレス</th>
        <th width="130">MACアドレス</th>
        <th width="100">接続ポート</th>
        <th>コメント</th>
        <th width="100">状態</th>
        <th width="200">起動方法</th>
        <th width="100">削除</th>
   </tr>
</tbody></table>
<!--
<div align="right" style="padding-top: 9px;">
    <input type="button" class="filter_button" value="通信を遮断(15秒)" onclick="setRejectFilter();"/><br>
    <div id="filter_status"></div>
</div>
-->
<table border="0" id="target_list_cnt"><tbody>
</tbody></table>
</div>

</body>
</html>

おわりに

とりあえず最低限の追加・変更で動作するようにした。以前作成したルーター設置のWOL機能はluaで作成しているが,起動したことが判らないので丁度良いサンプルに出会えた。デモ用なのでビジュアルはしっかり作りこまれておりさすがヤマハだと感心した。自分用に作る際はここまでにはしないでかなり手抜きをするだろうな。PoEからWOL動作に変更したことによりメッセージ表示等の「ON」「OFF」等は修正したが,その他のPoE関係のコードは残しているので,今後変更が必要だが,自分用なのでこのままでも良いか。自動・手動のラジオボタンはデフォルト手動に変更したが,自動で登録するとサーバー停止と同時にマジックパケットが送信されるので,まず使用することはないので消しても良かった。PoEスイッチのSWX2200-8PoEが無くてもWOL動作するが,PoE監視ツールとした場合は画面を表示しておかないと自動復旧(自動起動)しないので,24時間監視でないと意味がない。luaで常時監視させるか。
しかし,ルーターからget出来ない原因がわからない。暇なときに調べてみるか。案外凡ミスのような気がするが,データがサーバー保存されているので,テキストエディタで簡単に登録・修正できるのは良かった。

【後記】ルーターを使用しないでサーバーのみで完結するシステムに変更した。

参考

ダウンロードファイル一覧

poe_demo.zipを解凍
1 camera.png
2 custom.css     
3 database.txt    
4 deletelog.lua    
5 index.html        デモ用なので一部変更
6 jquery-1.11.1.min.js 
7 kconv.lua     
8 kconv_utable.lua  
9 log.lua       
10 log.png
11 logo.png
12 logout.html    
13 logout.js     
14 monitor.js       メインの変更 JavaScript
15 ping.lua       
16 register_icon.png
17 save.lua
18 skeleton.png

参考関連サイト

PoE受電端末監視/自動再起動デモ (yamaha.co.jp)
ヤマハネットワーク製品: 特集: 特別インタビュー LANマップ+PoE受電機器の自動復旧レポート | SCSK株式会社

最終形2023/11/12

パソコンの起動・停止監視ツール – na-blog (na-3.com)