glitch
node-red
node-red-dashboard
Node-REDDay 13

2クリックでNode-REDを立ち上げて、我が子(自作ノード)の様子をダッシュボードで窺う。

More than 1 year has passed since last update.

他の記事 でも、 Glitch のことを書いていて、使い回しになってしまったが、もう少し真剣に触ってみた。

自作ノードの npm からのダウンロード数を日別と累積で可視化した。
dashboard.png

2クリックで、 Glitch 上に Node-RED を立ち上げる。

  • 1クリック目 Remix on Glitch (← 本当にこのページのこのボタンを押す。)

しばらく待つ。30秒くらい。

  • 2クリック目は、1クリック目の遷移先ページでこれ[ image.png ]を押す。

上記の [Remix on Glitch] ボタンは下記で作成。
Generate GitHub Import Button

下記画面は、1クリック目で遷移する画面。
(下記状態は、30秒程度待ってから、2クリック目ができる状態。)
画面左上のプロジェクト名 fast-dagger (自動で一意な文字列になっている)をクリックすると、ペロってメニューが現れるので、その一番上で、プロジェクト名を変更できる。これは、アプリのサブドメイン部分になる。
image.png

  • デフォルトのログインは、下記で。
    • Username: admin
    • Password: password

qiitanoderedlogin.png

qiitanodered.png

Glitch の料金

Glitch for Platforms - Delivering Developer Success

  • Community
    • Free
    • Real-time Pair Programming
    • Unlimited Project Hosting & Remixing
    • Store Environment Variables & API Keys
    • GitHub Import/Export
    • Remix on Glitch Button
    • Glitch Help
  • Team
    • $999
  • Business
    • Contact Us

Glitch の制限

Glitch – Frequently Asked Questions

  • Projects created by anonymous users expire after 5 days (login via GitHub or Facebook to keep your projects around).
  • 匿名ユーザーによって作成されたプロジェクトは、5日後に期限切れになります(GitHubまたはFacebook経由でログインしてプロジェクトを継続する)。

上記のやり方で、さくっと Node-RED は立ち上がるが、 Glitch にログインしていないと、アプリが5日間で期限切れになるらしい。
ログインはすぐできるので、ログインしちゃう。

  • Projects sleep after 5 minutes if they are not used.
  • プロジェクトが使用されていない場合、5分後にスリープ状態になります。

スリープ状態で、おそらくサーバが止まる。 inject で1分おきに動かしておくとか、自分にHTTPリクエスト投げて、受け取ってを一定間隔でやれば継続するのかな?(未検証)

(追記 2017-12-14)inject で定期処理させているだけでは、スリープしてしまう。inject ノードで定期的(試したのは1分おき)に、 http ノードで、自分(例: https://fast-dagger.glitch.com/xxx )にリクエスト投げて、受け取っていれば、スリープはしていない模様。日次バッチとかさせたい場合は、これやらないとダメなのか?? cron 的な設定があるきがしてきた。また調査する。

  • Projects are limited to 4000 requests per hour, with a burst of 4000 requests (subsequent requests will return a 429 "Too Many Requests" response).
  • プロジェクトは1時間に4000リクエストに制限され、4000要求がバーストされます(その後のリクエストでは429個の 'Too Many Requests'レスポンスが返されます)。

1秒間に1回のリクエストはいけるのか。

  • Projects have a limit of 128MB of space on the container (though things written to '/tmp' don't count towards that, and we use compression to squeeze the most out of that space). As well as up to 512MB of assets storage.
  • プロジェクトはコンテナのスペースが128MBに制限されています(ただし、 '/ tmp'に書き込まれたものはそれに含まれません)。圧縮を使用してスペースを最大限に圧縮します。最大512MBのアセットストレージ

ふむふむ。

  • Projects only show up to 100 files in the file tree, the rest get hidden from view but still exist. Using .gitignore to temporarily remove some files from view can help work around this.
  • プロジェクトでは、ファイルツリーに100個までのファイルしか表示されず、残りのファイルは非表示になりますが、まだ存在します。 .gitignoreを使って一時的にいくつかのファイルをビューから削除すると、これを回避できます。

ほうほう。

  • Similarly, files above 200kb are hidden from the file tree (but still exist), and you're unable to paste data into the editor above that size too.
  • 同様に、200kbを超えるファイルはファイルツリーから隠されています(ただし、まだ存在しています)。そのサイズを上回るエディタにデータを貼り付けることはできません。

そかそか。

Glitch で立ち上げたアプリケーションの構成ファイル説明

server.js

ここのコード( Node-RED : Embedding into an existing app )を元に、認証や、Glitch に合わせた設定を付け加えている。

ポートは、 3000 じゃなくてもいい。Glitch のデフォルトが 3000 らしいからそれに合わせている。
FlowFile重要。Node-RED のデフォルトだと flows_<hostname>.json となるらしく、Glitch でスリープすると、おそらく hostname が変わる。なので、フローが消える。 flows.json に固定してあげることで解決のはず。
userDir: '/app/node-red' とすることで、Glitch 上にも flows.json だったりが表示されるようになる。
(表示されなくたったいいのだけれど。気分的に安心する。ただしリアルタイムでは反映されないので注意。)

adminAuthusernamepassword は、 .env で指定している。
.env に指定した値は、 process.env.XXX でとれる。

[参考]
https://nodered.org/docs/configuration

server.js
var http = require('http');
var express = require("express");
var RED = require("node-red");

// Create an Express app
var app = express();

// Add a simple route for static content served from 'public'
app.use("/", express.static("public"));

// Create a server
var server = http.createServer(app);

// Create the settings object - see default settings.js file for other options
var settings = {
  httpAdminRoot: "/",
  httpNodeRoot: "/api/",
  uiPort: 3000,
  functionGlobalContext: {    // enables global context
    // os:require('os'),
  },
  adminAuth: {
    type: "credentials",
    users: [{
      username: process.env.NODE_RED_USER,
      password: process.env.NODE_RED_PW,
      permissions: "*"
    }]
  },
  debugMaxLength: 1000,
  debugUseColors: true,
  flowFile: 'flows.json',
  userDir: '/app/node-red',
  nodesDir: '/app/node-red/nodes',
  ui: { path: "ui" },
  logging: {
    console: {
      level: "trace"
    }
  }
};

// Initialise the runtime with a server and settings
RED.init(server, settings);

// Serve the editor UI from /
app.use(settings.httpAdminRoot, RED.httpAdmin);

// Serve the http nodes UI from /
app.use(settings.httpNodeRoot, RED.httpNode);

server.listen(3000);

// Start the runtime
RED.start();

package.json

Glitch のデフォルトに、 "node-red": "^0.17.5" の行を付け加えた。

package.json
{
  "name": "node-red-for-glitch",
  "version": "0.0.1",
  "description": "Node-RED for Glitch.",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "engines": {
    "node": "8.x"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/high-u/node-red-for-glitch.git"
  },
  "keywords": [
    "node-red",
    "glitch",
    "node.js"
  ],
  "author": "high-u <zen.crazyd@gmail.com> (https://github.com/high-u)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/high-u/node-red-for-glitch/issues"
  },
  "homepage": "https://github.com/high-u/node-red-for-glitch#readme",
  "dependencies": {
    "express": "^4.16.2",
    "node-red": "^0.17.5"
  }
}

.env

NODE_RED_PW は、要注意で、 $\ でエスケープしてあげる必要がある。
今回は、サンプルとして、 gitリポジトリに含めたが、本来 .env はgitリポジトリに含めるべきでないことを念のため付け加える。

.env
# Environment Config

# store your secrets and config variables in here
# only invited collaborators will be able to see your .env values

# reference these in your code with process.env.SECRET

# Login Username
NODE_RED_USER="admin"

# Login Password
# escape doller (back-slash)
NODE_RED_PW="\$2a\$08\$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."

PORT=3000
PROJECT_DOMAIN=
PROJECT_ID=
PROJECT_INVITE_TOKEN=

# note: .env is a shell file so there can't be spaces around =

我が子を窺うダッシュボード

フロー

node-red-dashboard をインストールする。
下記フローを読み込んだら、一度、 dailysum のノードをダブルクリックで開いて、 Group の鉛筆アイコンを開かないと、反映されなかった。(なんでだろ)

flows.json
[{"id":"93f7c8d1.045b58","type":"ui_chart","z":"fa0ebd82.96c16","name":"daily","group":"cf85217.db9fbe","order":0,"width":0,"height":0,"label":"Daily","chartType":"line","legend":"true","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"1","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":450,"y":336,"wires":[["82a43b22.ba9f08"],["82a43b22.ba9f08"]]},{"id":"3d43de69.714c22","type":"inject","z":"fa0ebd82.96c16","name":"inject","topic":"https://api.npmjs.org/downloads/range/2017-09-01:2017-12-31/","payload":"[]","payloadType":"json","repeat":"","crontab":"","once":true,"x":146,"y":80,"wires":[["a5d79a9d.adbd78"]]},{"id":"82a43b22.ba9f08","type":"debug","z":"fa0ebd82.96c16","name":"","active":true,"console":"false","complete":"false","x":614,"y":368,"wires":[]},{"id":"a5d79a9d.adbd78","type":"template","z":"fa0ebd82.96c16","name":"nodes","field":"payload","fieldType":"msg","format":"handlebars","syntax":"plain","template":"[\n\"node-red-contrib-increment\",\n\"node-red-contrib-salesforce-platform-event\",\n\"node-red-contrib-aws-sdk-anything\",\n\"node-red-contrib-separate-flow-json\",\n\"node-red-contrib-manage-flow-by-git\",\n\"node-red-contrib-process-env\"\n]","output":"json","x":290,"y":80,"wires":[["2cf0fc3.1cb1d04"]]},{"id":"a6235af2.a42cb8","type":"function","z":"fa0ebd82.96c16","name":"conv","func":"// https://github.com/node-red/node-red-dashboard/blob/master/Charts.md\n\nvar s = msg.payload.downloads;\n\nvar labels = [];\nvar series = [msg.payload.package];\nvar data = [];\nvar d = [];\n\ns.forEach(function (v, i) {\n    labels.push(v.day);\n    d.push(v.downloads);\n});\ndata.push(d);\n\nmsg.payload = {\"series\":series, \"data\":data, \"labels\":labels};\nreturn msg;","outputs":1,"noerr":0,"x":546,"y":208,"wires":[["b14ef949.490cf8"]]},{"id":"b8716554.a26728","type":"split","z":"fa0ebd82.96c16","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":210,"y":144,"wires":[["cafce2fa.b9958"]]},{"id":"9fb7b719.eca6c8","type":"http request","z":"fa0ebd82.96c16","name":"npm API","method":"GET","ret":"obj","url":"","tls":"","x":412,"y":208,"wires":[["a6235af2.a42cb8"]]},{"id":"b14ef949.490cf8","type":"join","z":"fa0ebd82.96c16","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","x":610,"y":272,"wires":[["5563f5.89d69c0c"]]},{"id":"af6f6234.d39f4","type":"function","z":"fa0ebd82.96c16","name":"union","func":"var p = msg.payload;\nvar chartdata = {\"series\": [],\"data\": [],\"labels\": []};\n\np.forEach(function(v,i){\n    chartdata.series.push(v.series[0]);\n    chartdata.data.push(v.data[0]);\n});\nchartdata.labels = p[0].labels;\n\nmsg.payload = [chartdata];\n\nreturn msg;","outputs":1,"noerr":0,"x":210,"y":336,"wires":[["93f7c8d1.045b58","fea57568.169828"]]},{"id":"2cf0fc3.1cb1d04","type":"link out","z":"fa0ebd82.96c16","name":"","links":["a2885e1b.2744d"],"x":399,"y":80,"wires":[]},{"id":"a2885e1b.2744d","type":"link in","z":"fa0ebd82.96c16","name":"","links":["2cf0fc3.1cb1d04"],"x":111,"y":144,"wires":[["b8716554.a26728"]]},{"id":"5563f5.89d69c0c","type":"link out","z":"fa0ebd82.96c16","name":"","links":["d3664632.fe1b78"],"x":703,"y":272,"wires":[]},{"id":"d3664632.fe1b78","type":"link in","z":"fa0ebd82.96c16","name":"","links":["5563f5.89d69c0c"],"x":111,"y":336,"wires":[["af6f6234.d39f4"]]},{"id":"fea57568.169828","type":"function","z":"fa0ebd82.96c16","name":"add","func":"var p = msg.payload[0].data;\n\nvar chartdata = p.map(function(pelement, pindex, parray) {\n    return pelement.map(function(melement, mindex, marray) {\n        return marray.filter(function(felement, findex, farray) {\n            return (findex <= mindex);\n        })\n        .reduce(function(previousValue, currentValue, index, array) {\n            return previousValue + currentValue;\n        });\n    });\n});\n\nmsg.payload[0].data = chartdata;\nreturn msg;","outputs":1,"noerr":0,"x":322,"y":400,"wires":[["49b7f21f.48991c"]]},{"id":"49b7f21f.48991c","type":"ui_chart","z":"fa0ebd82.96c16","name":"sum","group":"cf85217.db9fbe","order":0,"width":0,"height":0,"label":"Sum","chartType":"line","legend":"true","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"1","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":450,"y":400,"wires":[["82a43b22.ba9f08"],["82a43b22.ba9f08"]]},{"id":"cafce2fa.b9958","type":"change","z":"fa0ebd82.96c16","name":"add url","rules":[{"t":"set","p":"url","pt":"msg","to":"msg.topic & msg.payload","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":268,"y":208,"wires":[["9fb7b719.eca6c8"]]},{"id":"cf85217.db9fbe","type":"ui_group","z":"","name":"npm downloads","tab":"ff37f545.4fbac8","disp":true,"width":"12"},{"id":"ff37f545.4fbac8","type":"ui_tab","z":"","name":"Dashboard","icon":"dashboard","order":1}]

flows.png

function 内は、ゴチャゴチャやってるけど forEach 使った方が処理コストはおそらく低い。趣味で filter とかにしてる。

node-red-dashboard へ渡すJSON

下記 github のページに Stored data や Live data の使い方(データフォーマット)が書いてある。
node-red-dashboard/Charts.md at master · node-red/node-red-dashboard

で、ダッシュボードはこんな感じ。日々のダウンロード数と累積ダウンロード数をラインチャートに。

dashboard.png

我が子紹介

node-red-contrib-aws-sdk-anything

AWS SDK は基本パターンが一緒だから何でも使えるようにしてみた。
中には使えないパターンも存在するけど、結構いけるはず。
dynamodb とか s3 とか kinesis はいけてる。

node-red-contrib-manage-flow-by-git

フローのgit管理を試して作ったやつ。たぶん名前が悪く、期待してダウンロードした人も多かったのではと、反省しつつ、名前付けの難しさと、git需要を実感できたノードw

難産ノード

node-red-contrib-salesforce-platform-event

done() の重要さ知らなかった。処理帰って来ないっていう罠。
jsforceunsubscribe の使い方に惑わされる。
期限に追われて、ギリギリの誕生。

    node.on('close', function (done) {
      tpc.unsubscribe();
      done();
    });

ここに答えが。
Node-RED : JavaScript file