mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
2976 Commits
0.10.0
...
0.20.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f309a9d537 | ||
|
|
a786b37cb9 | ||
|
|
6a519a30a2 | ||
|
|
81ae552e69 | ||
|
|
0ec04a3624 | ||
|
|
81d5b47fce | ||
|
|
ed31a0cf15 | ||
|
|
201d1926bc | ||
|
|
9ee6655bfa | ||
|
|
c4beab6b0d | ||
|
|
34b6643913 | ||
|
|
98e391b867 | ||
|
|
19eb8e9a6d | ||
|
|
43b7aa40c3 | ||
|
|
747af44fc1 | ||
|
|
d5ef428edd | ||
|
|
5fa4d227b8 | ||
|
|
cc7e3b0c26 | ||
|
|
473a2ae275 | ||
|
|
7f5d47f39d | ||
|
|
6031f146aa | ||
|
|
020a469f3b | ||
|
|
091de3aa66 | ||
|
|
b837f7608c | ||
|
|
afe9367bac | ||
|
|
9bd9023cb6 | ||
|
|
8502cf8498 | ||
|
|
33dade0584 | ||
|
|
84cc2ad0fa | ||
|
|
dc2d3bc7c0 | ||
|
|
64df557423 | ||
|
|
715cc77e76 | ||
|
|
b80d1af3d7 | ||
|
|
f05f534fd2 | ||
|
|
c0837ead0e | ||
|
|
a1f135bd66 | ||
|
|
978f4ecc58 | ||
|
|
46a8d96997 | ||
|
|
c283224000 | ||
|
|
a6ef755139 | ||
|
|
29a257d17a | ||
|
|
368b76a183 | ||
|
|
8bb861124d | ||
|
|
2f884ec778 | ||
|
|
8c561e92c8 | ||
|
|
633b9180d7 | ||
|
|
0e2d0e1b6f | ||
|
|
ea4d65ceee | ||
|
|
d47ac84d2e | ||
|
|
a97759aa35 | ||
|
|
3fcfd4abdd | ||
|
|
6d771da9a9 | ||
|
|
6201247875 | ||
|
|
8c367bcc53 | ||
|
|
8198132ca7 | ||
|
|
cf3b4e9e63 | ||
|
|
987dbf8a92 | ||
|
|
acf8c9bc4a | ||
|
|
7173895d36 | ||
|
|
43530d4a5f | ||
|
|
fefabef9ee | ||
|
|
0dd40a941b | ||
|
|
24b6670bc4 | ||
|
|
76661abbf5 | ||
|
|
e9dc9eff9b | ||
|
|
2f160743bc | ||
|
|
98616e772c | ||
|
|
bc014fec9b | ||
|
|
732598d9d2 | ||
|
|
2979acd5b8 | ||
|
|
fd20cd524e | ||
|
|
7b80ae42e1 | ||
|
|
b7012674c6 | ||
|
|
c76bd39280 | ||
|
|
09cd710f66 | ||
|
|
d9aadf9d98 | ||
|
|
86716b5ffb | ||
|
|
96e3aab3b4 | ||
|
|
72c78fe3ad | ||
|
|
abe746020b | ||
|
|
8e1c15419c | ||
|
|
ee47646cf7 | ||
|
|
32d9acdaa5 | ||
|
|
4eb3bd496b | ||
|
|
3c4f4d27d6 | ||
|
|
2060af8a92 | ||
|
|
21bf74a467 | ||
|
|
677833a277 | ||
|
|
0b5e4f2dd7 | ||
|
|
06a1f30350 | ||
|
|
c1ff241550 | ||
|
|
5717f75eac | ||
|
|
6b3b68a4e5 | ||
|
|
8bda2d0add | ||
|
|
bc02c9573c | ||
|
|
8167608f04 | ||
|
|
514e31aef9 | ||
|
|
20a31a6d38 | ||
|
|
4f0aa1bc02 | ||
|
|
be0ef6e594 | ||
|
|
93a8dbd31a | ||
|
|
cf931e8ddf | ||
|
|
510bfbf268 | ||
|
|
2f93bb969b | ||
|
|
27365c9f7b | ||
|
|
b1d2e188f5 | ||
|
|
e4f67df2a1 | ||
|
|
e094ea3d2a | ||
|
|
7515b745b5 | ||
|
|
0e902a7e71 | ||
|
|
2dfb443625 | ||
|
|
e6e7747ae1 | ||
|
|
d80ea6c0f5 | ||
|
|
ac6e3988a8 | ||
|
|
bfd98f3767 | ||
|
|
cc8bc1339f | ||
|
|
542d1dc600 | ||
|
|
1c66c88f95 | ||
|
|
dc880c672a | ||
|
|
073f38c68c | ||
|
|
6a6d13b075 | ||
|
|
9bb7e72c69 | ||
|
|
7436e01188 | ||
|
|
3d272d0f10 | ||
|
|
ee66a12dad | ||
|
|
47de85b012 | ||
|
|
f85b63a972 | ||
|
|
e630919ef8 | ||
|
|
2e3fd49b40 | ||
|
|
dd54af2c08 | ||
|
|
737bf411ff | ||
|
|
5070b1a6b5 | ||
|
|
c849da92cf | ||
|
|
6a4e7201fe | ||
|
|
ffa6dea452 | ||
|
|
30c1d31a99 | ||
|
|
6934a2d5c3 | ||
|
|
b9906ced9a | ||
|
|
5500b4fe35 | ||
|
|
2353f12cd6 | ||
|
|
1b8cab0f58 | ||
|
|
869c1d4ea4 | ||
|
|
9b938195a8 | ||
|
|
bb3c52821a | ||
|
|
81c8ae95e2 | ||
|
|
6098570ab7 | ||
|
|
d033c24fe5 | ||
|
|
4995e52dd6 | ||
|
|
c685a31056 | ||
|
|
c32ce3bb7b | ||
|
|
c0d8f904b3 | ||
|
|
8dba0dac9e | ||
|
|
d0bf4a5329 | ||
|
|
7b28ba6078 | ||
|
|
e6466c3c3a | ||
|
|
d96d3064d6 | ||
|
|
d4589ed7e3 | ||
|
|
5cfc52ea18 | ||
|
|
3fcacd8339 | ||
|
|
49fe13f22f | ||
|
|
4d27ba1bda | ||
|
|
b715ac8bf4 | ||
|
|
d96049416f | ||
|
|
1c4df785fd | ||
|
|
8f94c5efeb | ||
|
|
7cffa1ece7 | ||
|
|
1ac18d7b33 | ||
|
|
1111d2518b | ||
|
|
ba7416450e | ||
|
|
3b02d36acb | ||
|
|
c2aa9a5337 | ||
|
|
70fb181b7b | ||
|
|
e64f4e3f39 | ||
|
|
e4d518749f | ||
|
|
7dcca2c907 | ||
|
|
4a027b8a79 | ||
|
|
c2c6e6080e | ||
|
|
4f87ebdf0a | ||
|
|
09abec15b1 | ||
|
|
33d0d12bc8 | ||
|
|
f488869635 | ||
|
|
6382564727 | ||
|
|
7476b4c7db | ||
|
|
daf3e6a47a | ||
|
|
19b6cba398 | ||
|
|
5bd3d12c7b | ||
|
|
c9db74ebca | ||
|
|
f8a88cc1a4 | ||
|
|
4a081bf125 | ||
|
|
8a68a3e861 | ||
|
|
7a9dd9ad9c | ||
|
|
6c01d0f9d8 | ||
|
|
45a53ac168 | ||
|
|
dabb2790c9 | ||
|
|
c66c5ea53c | ||
|
|
0a98ba6985 | ||
|
|
bb8e491856 | ||
|
|
5590d31336 | ||
|
|
dc7e48dc53 | ||
|
|
371d357218 | ||
|
|
f22960ad59 | ||
|
|
34ead436b0 | ||
|
|
808d5a75ae | ||
|
|
55e897faac | ||
|
|
5a5dda21e4 | ||
|
|
f085655daa | ||
|
|
5cf9c07b73 | ||
|
|
9751089807 | ||
|
|
211eeea05d | ||
|
|
b2516117f5 | ||
|
|
2816b3edae | ||
|
|
242398c724 | ||
|
|
289583325d | ||
|
|
0f793ebd65 | ||
|
|
ce014044ea | ||
|
|
1064e531f0 | ||
|
|
dc3128fb3e | ||
|
|
51a3521834 | ||
|
|
d40aa7260f | ||
|
|
fc8c4063f2 | ||
|
|
f204c77ba3 | ||
|
|
7a8545273c | ||
|
|
6a9575e9f4 | ||
|
|
c13e79e9c3 | ||
|
|
62088259ae | ||
|
|
925ebcc06e | ||
|
|
a20eaf852f | ||
|
|
84a6a5235e | ||
|
|
8235b7b96d | ||
|
|
0376e0d711 | ||
|
|
6bd0682e8c | ||
|
|
e24c22f9be | ||
|
|
673a6bbe2c | ||
|
|
cf32a33984 | ||
|
|
f4ca8cd738 | ||
|
|
98c1bc276d | ||
|
|
629536b562 | ||
|
|
422109868d | ||
|
|
bcc7834650 | ||
|
|
1161e4f6c1 | ||
|
|
14435c24ac | ||
|
|
22ede79799 | ||
|
|
6cb3699ee9 | ||
|
|
6c65d3830e | ||
|
|
cdcf39fe82 | ||
|
|
1441042458 | ||
|
|
71403e5acd | ||
|
|
3d70bc722a | ||
|
|
b2f50da322 | ||
|
|
2a50c66df8 | ||
|
|
8de47c0a6e | ||
|
|
023391e22a | ||
|
|
a4ddfd404f | ||
|
|
7307e558cb | ||
|
|
47356f5221 | ||
|
|
c6f8950b64 | ||
|
|
58c8311d56 | ||
|
|
071f4eacde | ||
|
|
f96bdc578e | ||
|
|
e03a0fffa9 | ||
|
|
8e2c12f8d9 | ||
|
|
114420e8fd | ||
|
|
ba49b2c681 | ||
|
|
5391fc962a | ||
|
|
7cec7ae608 | ||
|
|
d6211af5bd | ||
|
|
ef114e31c2 | ||
|
|
fd74a03479 | ||
|
|
428bf634e9 | ||
|
|
c49f722e4f | ||
|
|
d4d95a43b6 | ||
|
|
8789d983ed | ||
|
|
10faa96bcf | ||
|
|
42d31b9ee6 | ||
|
|
c9dc9b4fe9 | ||
|
|
a345089c8b | ||
|
|
67c268e13d | ||
|
|
f13e02a1a9 | ||
|
|
d887ab126b | ||
|
|
1a7868159a | ||
|
|
c6a1c8e8c4 | ||
|
|
fba339f666 | ||
|
|
09d41a9708 | ||
|
|
ce85c8d986 | ||
|
|
4630a162af | ||
|
|
fde157ff50 | ||
|
|
51f875c02d | ||
|
|
f0957c838f | ||
|
|
f5bebef37f | ||
|
|
80a15089b4 | ||
|
|
cb35604ef5 | ||
|
|
61681bb1d6 | ||
|
|
8edf399631 | ||
|
|
d5ffd1432f | ||
|
|
e73bf03615 | ||
|
|
665fe0e01e | ||
|
|
1a226c4dc6 | ||
|
|
a866aa9c18 | ||
|
|
d9089b798c | ||
|
|
d34ebd4d1b | ||
|
|
716aa74004 | ||
|
|
2aae76c9bc | ||
|
|
5fc3ca0e23 | ||
|
|
5bb27109bf | ||
|
|
7406ab6017 | ||
|
|
08fccc4e77 | ||
|
|
c1d50e82e1 | ||
|
|
9777af7cb5 | ||
|
|
fd86035865 | ||
|
|
a8ec032553 | ||
|
|
66ee27c5fa | ||
|
|
17a737ca88 | ||
|
|
f30ff7a2fd | ||
|
|
c102828a99 | ||
|
|
cb0e631b85 | ||
|
|
75e7c0e50d | ||
|
|
62b2adab78 | ||
|
|
fc0cf1ff51 | ||
|
|
0f4d46671f | ||
|
|
048f9c0294 | ||
|
|
0529eed0c9 | ||
|
|
ca77842b5b | ||
|
|
8c169dc82b | ||
|
|
195342f7db | ||
|
|
cfaaef7860 | ||
|
|
e939d5e96e | ||
|
|
6fa8b7f5f1 | ||
|
|
a2d03c14ae | ||
|
|
c667a0e74c | ||
|
|
8123828113 | ||
|
|
72b8dbb45b | ||
|
|
9f4628cf0a | ||
|
|
ec4d24af91 | ||
|
|
7703875740 | ||
|
|
6442bb8a13 | ||
|
|
51373f59e2 | ||
|
|
6cc56879d3 | ||
|
|
f29d7c9252 | ||
|
|
2f7f53ed96 | ||
|
|
da89460830 | ||
|
|
9e006d42bb | ||
|
|
4c02bab4ee | ||
|
|
5800ed41f1 | ||
|
|
18b5b4901f | ||
|
|
368418cf56 | ||
|
|
94031a52a5 | ||
|
|
d67f91e7ed | ||
|
|
3e6cadf3d8 | ||
|
|
f37697c4fb | ||
|
|
0c5a76b391 | ||
|
|
69448c7329 | ||
|
|
8e9815fb91 | ||
|
|
bf1afcfe8a | ||
|
|
2980818f0d | ||
|
|
9da58dbaf0 | ||
|
|
4cdd7978cf | ||
|
|
40d81358f4 | ||
|
|
c7b62aed91 | ||
|
|
55d71659f8 | ||
|
|
c0e7d6d826 | ||
|
|
f809377de8 | ||
|
|
9767bd9697 | ||
|
|
3a55528552 | ||
|
|
56197ffe3a | ||
|
|
0f0d0c046c | ||
|
|
8d5b546763 | ||
|
|
19c9707d62 | ||
|
|
ecc4973645 | ||
|
|
79e004a040 | ||
|
|
3169f93cc2 | ||
|
|
c1a1a73599 | ||
|
|
48308db45b | ||
|
|
3f37e96f78 | ||
|
|
db1b0ccb79 | ||
|
|
df161ce672 | ||
|
|
4e21a5e557 | ||
|
|
72fe30892e | ||
|
|
19fa69811b | ||
|
|
0ddb4c625d | ||
|
|
d373105b32 | ||
|
|
36dc1d2f97 | ||
|
|
11fa2cb35d | ||
|
|
546f07156f | ||
|
|
7e7117632d | ||
|
|
954226da0d | ||
|
|
38a1291c5b | ||
|
|
998bf92ad4 | ||
|
|
974ba40f28 | ||
|
|
e57d8ba0ef | ||
|
|
6b79c6135f | ||
|
|
28b311b7ed | ||
|
|
dcda513901 | ||
|
|
72c400794c | ||
|
|
a747d8c2d5 | ||
|
|
a3aec6b939 | ||
|
|
042409f870 | ||
|
|
5b8f4f4069 | ||
|
|
d132d63c1d | ||
|
|
4374506981 | ||
|
|
ef8b936069 | ||
|
|
36e3bfffb4 | ||
|
|
91a38bdb60 | ||
|
|
f169a68319 | ||
|
|
ee886f98dd | ||
|
|
a3826cc6a7 | ||
|
|
ba33b832ba | ||
|
|
f6c017176b | ||
|
|
7a01b115bb | ||
|
|
1dc021e871 | ||
|
|
c9f916ebab | ||
|
|
ff627fd128 | ||
|
|
bba57f8d2b | ||
|
|
695873d35a | ||
|
|
15da19dcea | ||
|
|
4312a01707 | ||
|
|
ecd8f97d8b | ||
|
|
d5bdc1600b | ||
|
|
dfa077fd5f | ||
|
|
06abe63fb1 | ||
|
|
5155770213 | ||
|
|
9d507b09ca | ||
|
|
9c4a712dc7 | ||
|
|
f1d5bbb036 | ||
|
|
69ed0aebc3 | ||
|
|
549e56e220 | ||
|
|
450f4d9a5a | ||
|
|
6533a9793c | ||
|
|
2000cadb17 | ||
|
|
083c321efa | ||
|
|
f64c4a981f | ||
|
|
3ac8ce03bf | ||
|
|
66fca8710e | ||
|
|
1e245ece46 | ||
|
|
81efce03ba | ||
|
|
4e549dd426 | ||
|
|
52f74ff7e0 | ||
|
|
3c71b815f5 | ||
|
|
9efd48fe51 | ||
|
|
4609ee75b6 | ||
|
|
963ea4177e | ||
|
|
17e6940a42 | ||
|
|
315a9ceba3 | ||
|
|
a2bdeedb09 | ||
|
|
da5700d2d7 | ||
|
|
90e7f30247 | ||
|
|
3ccf6ba892 | ||
|
|
e50cd5b745 | ||
|
|
db77be5d72 | ||
|
|
c36870c23e | ||
|
|
e9be007040 | ||
|
|
9e400d9aa6 | ||
|
|
490c8dae75 | ||
|
|
3bcffe375d | ||
|
|
9f81a591e1 | ||
|
|
3db5306c70 | ||
|
|
45029dd084 | ||
|
|
b01cd30339 | ||
|
|
09329e1104 | ||
|
|
ab0fc2ecfa | ||
|
|
bf5d36d6bd | ||
|
|
a29527ec96 | ||
|
|
45e7ad8049 | ||
|
|
4d54663efd | ||
|
|
29d386cc51 | ||
|
|
ba1a67969b | ||
|
|
390ea5419e | ||
|
|
0fdeec7cc4 | ||
|
|
e34d883e50 | ||
|
|
507871687b | ||
|
|
ed58f62cd1 | ||
|
|
94bc4e7125 | ||
|
|
5832f7930d | ||
|
|
0066a20c22 | ||
|
|
774e4bfced | ||
|
|
054c7a76a4 | ||
|
|
c7f3b77aac | ||
|
|
a6a4620374 | ||
|
|
5148c62d1c | ||
|
|
6fc863a91e | ||
|
|
e066a154a1 | ||
|
|
d432edaed2 | ||
|
|
8f34f4e80b | ||
|
|
39b751acf5 | ||
|
|
bd5e8ba961 | ||
|
|
ed20327c41 | ||
|
|
991c68c394 | ||
|
|
2acc31a4e7 | ||
|
|
b9733e3dfa | ||
|
|
bb106bfce7 | ||
|
|
daf1388a6a | ||
|
|
8226f1fa75 | ||
|
|
e675512fa3 | ||
|
|
65e67b6c3e | ||
|
|
7612481570 | ||
|
|
f6c7cb5804 | ||
|
|
2201c9062f | ||
|
|
ca3da262da | ||
|
|
5847f92bef | ||
|
|
31ee1be81e | ||
|
|
6bccdd015f | ||
|
|
cecea318da | ||
|
|
8663ec6880 | ||
|
|
9b03f128aa | ||
|
|
8a51f97616 | ||
|
|
ee74ed9ce9 | ||
|
|
0f947c756e | ||
|
|
cae7949a48 | ||
|
|
be58b614e1 | ||
|
|
b0a01fa4b2 | ||
|
|
9734228001 | ||
|
|
9df1d44bc4 | ||
|
|
13d887028a | ||
|
|
83a8979309 | ||
|
|
75c29f1cb7 | ||
|
|
d9d15e41c7 | ||
|
|
d3598d5854 | ||
|
|
3a8aaee5d7 | ||
|
|
4fcf57d42c | ||
|
|
adb0891335 | ||
|
|
d21e719cc1 | ||
|
|
5807ab82c1 | ||
|
|
65cb04da63 | ||
|
|
312e3611b1 | ||
|
|
46acc62279 | ||
|
|
529b358c9b | ||
|
|
7fca04404e | ||
|
|
3a1cc6a2be | ||
|
|
5b76c91004 | ||
|
|
cf87837f7d | ||
|
|
5a0a7b907b | ||
|
|
6a2b1669b3 | ||
|
|
ca8264b3f4 | ||
|
|
b44ecd8819 | ||
|
|
987942959e | ||
|
|
91992b48c1 | ||
|
|
c9a335a6f9 | ||
|
|
b7ed159b50 | ||
|
|
c72961a52a | ||
|
|
afe6afca36 | ||
|
|
050acd239c | ||
|
|
24505ee4f5 | ||
|
|
63a249aba3 | ||
|
|
7bd94df2a0 | ||
|
|
761161a8e5 | ||
|
|
513579a7ee | ||
|
|
7165483d83 | ||
|
|
f8bcf219cb | ||
|
|
590506e306 | ||
|
|
9a5439c580 | ||
|
|
6b2f5fbb19 | ||
|
|
051c147b41 | ||
|
|
9111adf15f | ||
|
|
ba18b27371 | ||
|
|
2a287b2ae6 | ||
|
|
fc9040f715 | ||
|
|
0029022ef6 | ||
|
|
94f728d3fd | ||
|
|
d53ced7830 | ||
|
|
053d8c44c2 | ||
|
|
9f5767ea16 | ||
|
|
e8d76b0555 | ||
|
|
c2675600f6 | ||
|
|
6f087b4ec1 | ||
|
|
e94708606d | ||
|
|
c248f1a762 | ||
|
|
28402b0894 | ||
|
|
7dd98e99f9 | ||
|
|
dba195b396 | ||
|
|
88b153bc12 | ||
|
|
d4a47dc974 | ||
|
|
fe3ea6edfd | ||
|
|
6c8fc4846b | ||
|
|
b14a0e0dde | ||
|
|
e982f5076f | ||
|
|
54d9656f09 | ||
|
|
42188b9f49 | ||
|
|
49da324c5d | ||
|
|
9bf87697fd | ||
|
|
f368f5a9c4 | ||
|
|
eea85485e6 | ||
|
|
1a544b3b82 | ||
|
|
8b38fe9fe0 | ||
|
|
1bf4addf63 | ||
|
|
407e16e900 | ||
|
|
6e9fe3248a | ||
|
|
d8cf86fd6f | ||
|
|
f8aa4a9588 | ||
|
|
c249907846 | ||
|
|
57c1524a9a | ||
|
|
d8d82e2ba3 | ||
|
|
807b512ef7 | ||
|
|
b2f06b6777 | ||
|
|
d7adff9a65 | ||
|
|
b0d7e11d48 | ||
|
|
fc9cdb61f2 | ||
|
|
9c00492dc2 | ||
|
|
1a6babd199 | ||
|
|
1b693eed37 | ||
|
|
afb566b6b4 | ||
|
|
f870e9ed3e | ||
|
|
4bcf13cb58 | ||
|
|
50e2dcbcd5 | ||
|
|
6104bb98f1 | ||
|
|
946a6d6041 | ||
|
|
372c213c2c | ||
|
|
a5a79d3ab7 | ||
|
|
e6c5cfb703 | ||
|
|
7843eccae8 | ||
|
|
7ca153abd0 | ||
|
|
33b4774c49 | ||
|
|
c243481432 | ||
|
|
80873e4ea9 | ||
|
|
4e4a1f11e6 | ||
|
|
9bbe405cd0 | ||
|
|
c440a4c730 | ||
|
|
a1251371d7 | ||
|
|
7d702e8332 | ||
|
|
43d7c8d48c | ||
|
|
7423583508 | ||
|
|
08b0838f9a | ||
|
|
038d821a7c | ||
|
|
6a218814d3 | ||
|
|
905f89b0f5 | ||
|
|
14882bda78 | ||
|
|
781fa4634b | ||
|
|
cdb173fd6e | ||
|
|
466cb4be89 | ||
|
|
c39e2ffd56 | ||
|
|
17bf09e276 | ||
|
|
bc01f9f8fd | ||
|
|
c0870c5694 | ||
|
|
3b5174a2ea | ||
|
|
af6885f3e8 | ||
|
|
e01996095f | ||
|
|
5d86f7b6ba | ||
|
|
8d6ac6406d | ||
|
|
40ff54f67e | ||
|
|
cce7ac09d0 | ||
|
|
73a18891c5 | ||
|
|
fe22cedc1d | ||
|
|
fa09c7c8b2 | ||
|
|
15e28e3cc0 | ||
|
|
2cb4f6b1fc | ||
|
|
b17a483b85 | ||
|
|
7c3e5443ab | ||
|
|
f95a2851c8 | ||
|
|
2b2eee352f | ||
|
|
11569d8056 | ||
|
|
bdf87452b6 | ||
|
|
0c6bf81c24 | ||
|
|
f2fa26fb07 | ||
|
|
f5e212ff1e | ||
|
|
461e6562ca | ||
|
|
fd67d08402 | ||
|
|
e6411d11b1 | ||
|
|
dd81d947fc | ||
|
|
23b887c30e | ||
|
|
c4eae3f130 | ||
|
|
41a04a2849 | ||
|
|
ed1d34e678 | ||
|
|
f44487338d | ||
|
|
7aced85a31 | ||
|
|
fbe0e2d6eb | ||
|
|
6e34f0697c | ||
|
|
a835f9f0cb | ||
|
|
16715673c3 | ||
|
|
c48c74f173 | ||
|
|
f262348497 | ||
|
|
7185bcd51f | ||
|
|
28d05e2449 | ||
|
|
7fafa21a1b | ||
|
|
84f598e143 | ||
|
|
e30f8628db | ||
|
|
0be9c88106 | ||
|
|
e046fc1ac5 | ||
|
|
3a476ac493 | ||
|
|
e33ec0cf50 | ||
|
|
b4b70a988e | ||
|
|
e66b381070 | ||
|
|
771b598c09 | ||
|
|
cd44f13171 | ||
|
|
aa6b72ac87 | ||
|
|
a467fe5ed7 | ||
|
|
467411c6c3 | ||
|
|
2648b7ca54 | ||
|
|
de35c7024a | ||
|
|
9d219c163d | ||
|
|
f7434b5ec8 | ||
|
|
5ed3360c0b | ||
|
|
6f5974f875 | ||
|
|
56db1da3cf | ||
|
|
fef71f29c4 | ||
|
|
d46b66878a | ||
|
|
6cad80c4ad | ||
|
|
68779caa2e | ||
|
|
2a122ed283 | ||
|
|
17c5fdf0d5 | ||
|
|
0835fdd0d1 | ||
|
|
f6274445a2 | ||
|
|
3b0300b834 | ||
|
|
4fbf1fe780 | ||
|
|
dcf44fed58 | ||
|
|
7136dc1c72 | ||
|
|
0e4cedbc5e | ||
|
|
dc139bcc30 | ||
|
|
b204b183de | ||
|
|
ab788bc1e3 | ||
|
|
95b4c8d515 | ||
|
|
b025644525 | ||
|
|
9e87a60597 | ||
|
|
5a70bea67a | ||
|
|
2a95af3928 | ||
|
|
0a0ca380d3 | ||
|
|
4cfbf7f71c | ||
|
|
de43148341 | ||
|
|
0a2aab7d68 | ||
|
|
745821c420 | ||
|
|
57c4c754d0 | ||
|
|
4b5c437533 | ||
|
|
8d63b6a1ed | ||
|
|
245a8adbf9 | ||
|
|
4f7d98aace | ||
|
|
b0c693cc3a | ||
|
|
b2cca10e8b | ||
|
|
a84b2ab5bb | ||
|
|
4565342b05 | ||
|
|
0ad54cc2d1 | ||
|
|
865853da19 | ||
|
|
392ed706fd | ||
|
|
0ff0f25aaf | ||
|
|
c157960846 | ||
|
|
a5c00b5c81 | ||
|
|
472bbdb59f | ||
|
|
7877093713 | ||
|
|
8cb2e51407 | ||
|
|
d5cee81fb6 | ||
|
|
bca020bc4d | ||
|
|
5069f2844c | ||
|
|
252df81f59 | ||
|
|
7f89a4a26f | ||
|
|
40f4167894 | ||
|
|
0ef16989cd | ||
|
|
3df3d6f516 | ||
|
|
10395ef254 | ||
|
|
83854c28db | ||
|
|
fcbea2629c | ||
|
|
522360dcb7 | ||
|
|
26bc142cc2 | ||
|
|
a4eb8e11c3 | ||
|
|
9fd5d1db56 | ||
|
|
1d05b4c981 | ||
|
|
61f6535be8 | ||
|
|
7dd329b5ee | ||
|
|
b761904424 | ||
|
|
36105412b1 | ||
|
|
184b1b018c | ||
|
|
f3e1b85d82 | ||
|
|
626d012775 | ||
|
|
9ad9c0ec6a | ||
|
|
e13fed9fc6 | ||
|
|
eb6d093e56 | ||
|
|
979713c4db | ||
|
|
af1ea610ea | ||
|
|
d4d9190919 | ||
|
|
4d3d1a02a8 | ||
|
|
30c2aa96d6 | ||
|
|
4edb1f80b0 | ||
|
|
0a82459233 | ||
|
|
db87b0dfa5 | ||
|
|
e41d5c249f | ||
|
|
f82a779817 | ||
|
|
cd42cf7583 | ||
|
|
2d5980ff2a | ||
|
|
df8a8ea204 | ||
|
|
8957d33e49 | ||
|
|
d49c7a3adb | ||
|
|
28fe1e4c8f | ||
|
|
0c7f4e2168 | ||
|
|
4fdd09a262 | ||
|
|
d6878512c4 | ||
|
|
8b1b8250ff | ||
|
|
9dccbf747e | ||
|
|
08727e1938 | ||
|
|
7584820987 | ||
|
|
d572356642 | ||
|
|
3b5a2815a9 | ||
|
|
f3cf01df25 | ||
|
|
63e6e64ad3 | ||
|
|
e8d7b48bff | ||
|
|
12944d1ebd | ||
|
|
fa1ff6e393 | ||
|
|
98546b6e6a | ||
|
|
2fef6fd1fa | ||
|
|
20cf91f1dc | ||
|
|
2efa78d590 | ||
|
|
880af0671a | ||
|
|
62471e4531 | ||
|
|
b15f8535f8 | ||
|
|
7a3a4493da | ||
|
|
11078235c4 | ||
|
|
f3e05cd08a | ||
|
|
0ca3cabbe8 | ||
|
|
44a75c1291 | ||
|
|
4a4513a746 | ||
|
|
60ff8660de | ||
|
|
6fa0d671c0 | ||
|
|
f478d7c9f0 | ||
|
|
53e3e08d70 | ||
|
|
c4d1ccb6f5 | ||
|
|
e3520309fc | ||
|
|
27bf72372e | ||
|
|
ae4b1b17a9 | ||
|
|
94cb03f4b5 | ||
|
|
e691351976 | ||
|
|
3190de873e | ||
|
|
b22956bd99 | ||
|
|
42516206d9 | ||
|
|
fc4edde6e6 | ||
|
|
54cc04fd96 | ||
|
|
80062b6a62 | ||
|
|
99af79fcf3 | ||
|
|
11d87205d7 | ||
|
|
5866d414ce | ||
|
|
9a972b0b8a | ||
|
|
e6aeeea8c1 | ||
|
|
5d064aa1d7 | ||
|
|
34832d5942 | ||
|
|
e3b1179a21 | ||
|
|
f94a36613c | ||
|
|
efc3cc24f4 | ||
|
|
b47f8aaf70 | ||
|
|
94ca4607bc | ||
|
|
2dab1d3e6e | ||
|
|
825b0fb22f | ||
|
|
1cdb039ea2 | ||
|
|
7409cb3abb | ||
|
|
e8e8f70c27 | ||
|
|
e8a637498d | ||
|
|
e1195ac00a | ||
|
|
6cd9ccc37c | ||
|
|
25345302e8 | ||
|
|
eccd5e9801 | ||
|
|
ff355af9f2 | ||
|
|
5967f4b0d4 | ||
|
|
ff18618032 | ||
|
|
20f03c356c | ||
|
|
27fdc9e56e | ||
|
|
52d9578a19 | ||
|
|
f4c2938b41 | ||
|
|
9f703de5ec | ||
|
|
a327fd85e2 | ||
|
|
9d22a86ec8 | ||
|
|
29e0b194dd | ||
|
|
ae9cf13fc2 | ||
|
|
64ae67586a | ||
|
|
838c7a5e89 | ||
|
|
89bfc90f40 | ||
|
|
acad9f57f9 | ||
|
|
0d08dc410e | ||
|
|
ebb3fb96cd | ||
|
|
f31f23ff07 | ||
|
|
d2aa3d1868 | ||
|
|
c9e2fce94d | ||
|
|
8b0e76dd55 | ||
|
|
884618adfe | ||
|
|
6e2e36e7a0 | ||
|
|
9994df9601 | ||
|
|
98f7271ac8 | ||
|
|
087cd121b8 | ||
|
|
2d52527fb4 | ||
|
|
fe289e62b5 | ||
|
|
2845475e3f | ||
|
|
b307492487 | ||
|
|
d48284f7ea | ||
|
|
7e416797e9 | ||
|
|
5d54ca7477 | ||
|
|
b979b4e61a | ||
|
|
2527f7984a | ||
|
|
d9350b2362 | ||
|
|
bd0b903f1a | ||
|
|
f243c0df19 | ||
|
|
7482978953 | ||
|
|
77966689d4 | ||
|
|
cf43939d65 | ||
|
|
391ac4b351 | ||
|
|
e1e48aadd9 | ||
|
|
0681f206c4 | ||
|
|
d257c6f3d3 | ||
|
|
fa45c82cdc | ||
|
|
e805b58da6 | ||
|
|
943976d207 | ||
|
|
3a2e5a6ccd | ||
|
|
35ef036246 | ||
|
|
e09c3bbdd3 | ||
|
|
3b12076d4b | ||
|
|
cfcf78ae28 | ||
|
|
341ff9bf5c | ||
|
|
10d8ca30b0 | ||
|
|
4ebb5d099e | ||
|
|
1e82b66bf0 | ||
|
|
06a5e4273b | ||
|
|
e123e7b0b0 | ||
|
|
aeadc40c65 | ||
|
|
7ef418ec52 | ||
|
|
2ed52820b6 | ||
|
|
e8fd7484b6 | ||
|
|
ce5242cfe8 | ||
|
|
af947879d8 | ||
|
|
3ed112cde6 | ||
|
|
99c6a9eccd | ||
|
|
2029f6ea0a | ||
|
|
e984e1f30f | ||
|
|
f21260370f | ||
|
|
fdae75c99b | ||
|
|
a0489f2a0d | ||
|
|
0123eacbdb | ||
|
|
53401b6aa7 | ||
|
|
9a5139f452 | ||
|
|
2ee0c8c228 | ||
|
|
c53562cc9c | ||
|
|
ec5d7c2e5c | ||
|
|
d6fc258485 | ||
|
|
f953612695 | ||
|
|
2ab93acca8 | ||
|
|
326c6c496e | ||
|
|
9f7f50664c | ||
|
|
f6f1436123 | ||
|
|
c3c519419d | ||
|
|
e569a80b72 | ||
|
|
284f437c1a | ||
|
|
1fd44a9958 | ||
|
|
cad34742f6 | ||
|
|
323359b3c8 | ||
|
|
35db8b45f0 | ||
|
|
50ae815ceb | ||
|
|
1a4389c90d | ||
|
|
fdaa5ce1da | ||
|
|
09d9936aed | ||
|
|
b3f6109b1c | ||
|
|
5fb3ffc240 | ||
|
|
360db252bb | ||
|
|
0b6e290271 | ||
|
|
7f0174e6db | ||
|
|
a25dad6c2e | ||
|
|
6191a49ed3 | ||
|
|
6252b075bc | ||
|
|
7face138fd | ||
|
|
382b83b093 | ||
|
|
691687d1bc | ||
|
|
5814b80a72 | ||
|
|
e147fbb1fa | ||
|
|
b9e256adfa | ||
|
|
3b7bf04e22 | ||
|
|
43408a724c | ||
|
|
5fbd5bf9e2 | ||
|
|
5e87828b29 | ||
|
|
9066cedc29 | ||
|
|
aa1cf0b228 | ||
|
|
06a6a4408f | ||
|
|
d5619d2b9d | ||
|
|
2f6ac42efe | ||
|
|
0bba3dd83d | ||
|
|
abe60b62e6 | ||
|
|
555b7df986 | ||
|
|
b3786700e6 | ||
|
|
ce9643d21b | ||
|
|
4a5cb7f2f5 | ||
|
|
42a7e902e6 | ||
|
|
aebe080e85 | ||
|
|
c316284924 | ||
|
|
8d98b228ab | ||
|
|
5b4c42ff05 | ||
|
|
a596a4551a | ||
|
|
0968f96982 | ||
|
|
5931e13b9c | ||
|
|
415c768ae4 | ||
|
|
b4c8bf21d5 | ||
|
|
5fe5db603d | ||
|
|
9f7dd7f5d4 | ||
|
|
e6d32aab7b | ||
|
|
08bd6d963c | ||
|
|
ff05fb14a6 | ||
|
|
22d942b705 | ||
|
|
0526372f28 | ||
|
|
f99051906a | ||
|
|
d1f7fd8bfd | ||
|
|
fc1436a96d | ||
|
|
d21568497b | ||
|
|
df4beef060 | ||
|
|
a52f195d41 | ||
|
|
419019a656 | ||
|
|
42b5635485 | ||
|
|
a8fc5b01f3 | ||
|
|
ead841d844 | ||
|
|
67d7930aef | ||
|
|
6f69995f4e | ||
|
|
05252fa239 | ||
|
|
1377439bb0 | ||
|
|
407123a280 | ||
|
|
750dd590c8 | ||
|
|
44112a9d18 | ||
|
|
b220bf0d99 | ||
|
|
d0d93d7070 | ||
|
|
4117961236 | ||
|
|
68a3d71ee6 | ||
|
|
bf5d741f0d | ||
|
|
55a33bc408 | ||
|
|
3ec35ed119 | ||
|
|
3d8d6953ec | ||
|
|
528db67c34 | ||
|
|
b847e962aa | ||
|
|
bbb9a3c63b | ||
|
|
322cebc48c | ||
|
|
1cceb3d880 | ||
|
|
effc64db9a | ||
|
|
7e5bd5f2c1 | ||
|
|
e32cc4d1af | ||
|
|
09a3cd850e | ||
|
|
b0c876019a | ||
|
|
6725f870d2 | ||
|
|
0e5adc1f0a | ||
|
|
57ebb93dc0 | ||
|
|
05dc0bfa1d | ||
|
|
2d0264116c | ||
|
|
3938550ea8 | ||
|
|
9f0c567794 | ||
|
|
8672fcd2bb | ||
|
|
3f2a92e801 | ||
|
|
771e43583a | ||
|
|
9353d5c1c4 | ||
|
|
5e462f0f02 | ||
|
|
a25f6fec9f | ||
|
|
519edce0ed | ||
|
|
0bc7702d95 | ||
|
|
18be0d6d26 | ||
|
|
1d4a435f20 | ||
|
|
34e46fc6d3 | ||
|
|
50956c51f7 | ||
|
|
dd7bb28b6a | ||
|
|
8516f41ba8 | ||
|
|
15c3cc60f6 | ||
|
|
716bca211b | ||
|
|
8179813fe1 | ||
|
|
d355de509b | ||
|
|
b04a2d4c08 | ||
|
|
6d3232a4f0 | ||
|
|
2753075180 | ||
|
|
d0166b25e4 | ||
|
|
73ee657d74 | ||
|
|
45913e5ee8 | ||
|
|
e6369820a9 | ||
|
|
22a5b339f7 | ||
|
|
2cea3b6435 | ||
|
|
5d2d06fb3e | ||
|
|
0dd7bc7fb9 | ||
|
|
0b0005337c | ||
|
|
d26fb02bb9 | ||
|
|
af683835d9 | ||
|
|
c43647ca86 | ||
|
|
6d02e70025 | ||
|
|
5498c6f87d | ||
|
|
11f59bc3ac | ||
|
|
94cb7de79f | ||
|
|
838f45775b | ||
|
|
1c1422e4b5 | ||
|
|
cd8ca6fc62 | ||
|
|
b7a0a9d7c2 | ||
|
|
7822ab113a | ||
|
|
e250a91f09 | ||
|
|
92a65dcda5 | ||
|
|
4b129d94e4 | ||
|
|
e7960d1d44 | ||
|
|
95589307cd | ||
|
|
20a0e4f3e0 | ||
|
|
6a9213da64 | ||
|
|
7a89e3cf33 | ||
|
|
64607df929 | ||
|
|
a62a1012fa | ||
|
|
14efd0b2f9 | ||
|
|
7ad2192df8 | ||
|
|
3cb5cbd8d5 | ||
|
|
cc9011cd68 | ||
|
|
dc3d89008d | ||
|
|
1893642187 | ||
|
|
a9ece5772d | ||
|
|
cf34716a57 | ||
|
|
1337831061 | ||
|
|
757e72100d | ||
|
|
a75b819858 | ||
|
|
da4a0f09ed | ||
|
|
87d847a074 | ||
|
|
84711beec0 | ||
|
|
f3cf58c8ff | ||
|
|
cf40497e6e | ||
|
|
dfebc4b78d | ||
|
|
15f41a2e7c | ||
|
|
ad6e55ca17 | ||
|
|
6b466d217a | ||
|
|
00dcb304c7 | ||
|
|
c6fb3d6f41 | ||
|
|
ac3143811f | ||
|
|
7e27dd7678 | ||
|
|
c2508296a5 | ||
|
|
a9b50ce6fc | ||
|
|
eac98a6d4d | ||
|
|
7e2b2a9a02 | ||
|
|
353de471eb | ||
|
|
85fc20b52d | ||
|
|
cc25a781f8 | ||
|
|
fc3012ba72 | ||
|
|
d93a92c1c8 | ||
|
|
f7f795f58a | ||
|
|
2700f8cdd2 | ||
|
|
6310de0d20 | ||
|
|
218794be77 | ||
|
|
af71ae649b | ||
|
|
9bc72c1a06 | ||
|
|
8d7c157751 | ||
|
|
558a66fbe5 | ||
|
|
f95b414d22 | ||
|
|
e793a1e1aa | ||
|
|
b76010cb5a | ||
|
|
52475df783 | ||
|
|
1f3f32d377 | ||
|
|
3f5ba10354 | ||
|
|
25f4a018d9 | ||
|
|
a11a279c00 | ||
|
|
fd4fdb31b5 | ||
|
|
1921796d6d | ||
|
|
543a2b9dc7 | ||
|
|
dd23e03342 | ||
|
|
5307c74f85 | ||
|
|
d701c406e2 | ||
|
|
3ba56a0a65 | ||
|
|
4adafb6d1e | ||
|
|
4453a51211 | ||
|
|
14429d2943 | ||
|
|
1a62a7831b | ||
|
|
242e35c212 | ||
|
|
ea763fdfd5 | ||
|
|
e762b7ff48 | ||
|
|
298068b2b9 | ||
|
|
cb4120ec4b | ||
|
|
5cfbb87bee | ||
|
|
9e472ed83c | ||
|
|
ebca753fc4 | ||
|
|
548f45cd56 | ||
|
|
8ffabf1813 | ||
|
|
1f40d4f941 | ||
|
|
41582045d0 | ||
|
|
fd9e3fc03a | ||
|
|
8c42b2bdb4 | ||
|
|
7b1787fdbb | ||
|
|
71fee0025d | ||
|
|
1204cf1ba0 | ||
|
|
7bd8d8c3ae | ||
|
|
2c4d5fa38d | ||
|
|
bedb2d943e | ||
|
|
a3640bd9bf | ||
|
|
7c0b9ffe06 | ||
|
|
161c7d30ca | ||
|
|
4ff6e792cd | ||
|
|
af5df890a5 | ||
|
|
9ba011003a | ||
|
|
bb168d35a8 | ||
|
|
3306d30094 | ||
|
|
6516e0dfd2 | ||
|
|
00a396014b | ||
|
|
13356047dc | ||
|
|
8a6488b067 | ||
|
|
1c2ea56f42 | ||
|
|
1d7ae300e2 | ||
|
|
6013e186ed | ||
|
|
207d3d3340 | ||
|
|
5a6cde1446 | ||
|
|
63f7d826bc | ||
|
|
ff8773f6bd | ||
|
|
a868cb97d9 | ||
|
|
915d73e6f2 | ||
|
|
5f4f6e37b5 | ||
|
|
9c350311e8 | ||
|
|
3c6ba72a2a | ||
|
|
816442f5f0 | ||
|
|
3b51d18ce7 | ||
|
|
6696b6661a | ||
|
|
8c87478636 | ||
|
|
d870b072d7 | ||
|
|
2ea2af7d2a | ||
|
|
f737ea96f3 | ||
|
|
05f90394db | ||
|
|
c24b0c6bb4 | ||
|
|
e07a4dc7ba | ||
|
|
fc6748a46b | ||
|
|
7697c46652 | ||
|
|
ed52e5afd1 | ||
|
|
33a5b84181 | ||
|
|
c09a407f4c | ||
|
|
d35784ec61 | ||
|
|
53e012f296 | ||
|
|
2a9d0a5e7d | ||
|
|
474f4572f2 | ||
|
|
bf57cb209f | ||
|
|
9bc41c1709 | ||
|
|
fe10b8650f | ||
|
|
3a311c9584 | ||
|
|
d1106f53e0 | ||
|
|
a3a1bba5ef | ||
|
|
028d66befc | ||
|
|
bb59cd5742 | ||
|
|
604e3068b2 | ||
|
|
27f1d3b704 | ||
|
|
d007623347 | ||
|
|
6a5cf7a1fa | ||
|
|
3adfe249b0 | ||
|
|
923893e160 | ||
|
|
256e5360d4 | ||
|
|
304c597a2f | ||
|
|
f86d3a69d2 | ||
|
|
d7c8adfd82 | ||
|
|
55cd069043 | ||
|
|
3ca0e9c420 | ||
|
|
1dd4323613 | ||
|
|
d78916f85f | ||
|
|
1840d15397 | ||
|
|
b98d1216b1 | ||
|
|
27db727321 | ||
|
|
3f6b1f6ccb | ||
|
|
6d633b372a | ||
|
|
91352e855a | ||
|
|
8bb9b594cf | ||
|
|
6d2fd2e641 | ||
|
|
422fbcb0b7 | ||
|
|
afce106186 | ||
|
|
f21c8154ed | ||
|
|
3988a648d6 | ||
|
|
1b632894d3 | ||
|
|
5e128f89f6 | ||
|
|
fff0b15ae5 | ||
|
|
a7e14f1093 | ||
|
|
94eeaeb8d3 | ||
|
|
64191e8303 | ||
|
|
21cfb71617 | ||
|
|
806457063f | ||
|
|
b9213b73bd | ||
|
|
d7f0102aa2 | ||
|
|
f09e61a59a | ||
|
|
d426aaa88a | ||
|
|
19e45389e1 | ||
|
|
6d2389945b | ||
|
|
14c48253f6 | ||
|
|
e5ff25b92d | ||
|
|
5c88888e02 | ||
|
|
10057de9b3 | ||
|
|
cc88ebd2b9 | ||
|
|
6baedf909d | ||
|
|
f39d9d6f1b | ||
|
|
ab61a95f83 | ||
|
|
10ceed30c6 | ||
|
|
2b9aa94f3a | ||
|
|
848fb975ed | ||
|
|
d7f59dac84 | ||
|
|
dd47e615ee | ||
|
|
8f2f7ea1a5 | ||
|
|
80a8efd8ce | ||
|
|
d9dce77ef4 | ||
|
|
ce7053a1fe | ||
|
|
0db1530171 | ||
|
|
3745504107 | ||
|
|
57533fd831 | ||
|
|
f57a0d4d6b | ||
|
|
22772ca33e | ||
|
|
dba6ff1d51 | ||
|
|
40146dedaf | ||
|
|
387b822f53 | ||
|
|
b9a3563e5b | ||
|
|
3d6468326a | ||
|
|
298e37ec53 | ||
|
|
5b137c457b | ||
|
|
5218a3fbac | ||
|
|
4569cb432d | ||
|
|
611e598756 | ||
|
|
937d79d28f | ||
|
|
23c2a771d3 | ||
|
|
58a890e836 | ||
|
|
6a869e120c | ||
|
|
ae7c298b1a | ||
|
|
53bfe12ac1 | ||
|
|
140ea683a6 | ||
|
|
0634a97598 | ||
|
|
3479c794de | ||
|
|
89cad116f7 | ||
|
|
19c84eb694 | ||
|
|
eae390acf5 | ||
|
|
10567afbb9 | ||
|
|
51bad3bf3c | ||
|
|
9134d8841d | ||
|
|
e9a026c131 | ||
|
|
9a2fd0e2b2 | ||
|
|
522f7e6844 | ||
|
|
cb4f46decc | ||
|
|
81256279a8 | ||
|
|
039bd1ddc0 | ||
|
|
0791d4797f | ||
|
|
6a06142e1e | ||
|
|
ef53dca062 | ||
|
|
6ce761edda | ||
|
|
d8fd218409 | ||
|
|
edc2310599 | ||
|
|
b1cd13d629 | ||
|
|
b81940351f | ||
|
|
a42e99c4aa | ||
|
|
ff40b521b7 | ||
|
|
85392496e7 | ||
|
|
29cae9975e | ||
|
|
170d6b28f8 | ||
|
|
9a8b404054 | ||
|
|
41af5187aa | ||
|
|
a844ca161f | ||
|
|
e09efba313 | ||
|
|
96a0dbea2d | ||
|
|
5b3b5271ad | ||
|
|
d7d13c12fe | ||
|
|
54220d0e71 | ||
|
|
4a2e3586f1 | ||
|
|
f808e85da9 | ||
|
|
1671d1f580 | ||
|
|
7de1bf9d95 | ||
|
|
7368b0cefb | ||
|
|
4af43d676a | ||
|
|
67dc848b2d | ||
|
|
7ec8f0d26b | ||
|
|
eaf08a9971 | ||
|
|
5bdb9e972e | ||
|
|
d4d87054c4 | ||
|
|
0f93929544 | ||
|
|
1c0e794f87 | ||
|
|
f9bce5a5f9 | ||
|
|
797ae096c8 | ||
|
|
6d76918424 | ||
|
|
2aced893c6 | ||
|
|
f0373cd789 | ||
|
|
2f88dc64fc | ||
|
|
781ca77794 | ||
|
|
c6e453fb00 | ||
|
|
a40b3dd377 | ||
|
|
096b3534d8 | ||
|
|
5324244c55 | ||
|
|
993f1dc853 | ||
|
|
d8a4e9e1ab | ||
|
|
b3ffd33507 | ||
|
|
c93870316c | ||
|
|
fc9906624e | ||
|
|
ba6209ba54 | ||
|
|
f9769a73fe | ||
|
|
3a2f56cb95 | ||
|
|
a4d33879dc | ||
|
|
e2a91d1ea9 | ||
|
|
f30f80d117 | ||
|
|
266274135e | ||
|
|
a10439b67c | ||
|
|
0fd8d0e2bf | ||
|
|
47e2707fd3 | ||
|
|
f7bb4a7d60 | ||
|
|
6102a31a31 | ||
|
|
1692c3b102 | ||
|
|
ac60725d2a | ||
|
|
1542f73fa5 | ||
|
|
70a22187f7 | ||
|
|
347e598715 | ||
|
|
a737810c50 | ||
|
|
92654a71fb | ||
|
|
18615640e0 | ||
|
|
b8c80a2310 | ||
|
|
c34c98386e | ||
|
|
d8a3d2793f | ||
|
|
360b0d9997 | ||
|
|
356f46aaf4 | ||
|
|
87ac0507d9 | ||
|
|
63657c18e2 | ||
|
|
817f92a50e | ||
|
|
304be96dd6 | ||
|
|
9639081e7e | ||
|
|
dca553048a | ||
|
|
6201ed4d55 | ||
|
|
6db2c04585 | ||
|
|
f3840512ba | ||
|
|
9a6cf58565 | ||
|
|
78076122ba | ||
|
|
7429f66d6b | ||
|
|
e59eff83b9 | ||
|
|
2083d85afa | ||
|
|
4c9f1369c8 | ||
|
|
adca1d7855 | ||
|
|
dfc4e99560 | ||
|
|
344076c943 | ||
|
|
710f1e2ca0 | ||
|
|
74ea85d19c | ||
|
|
dded98e30c | ||
|
|
160c27c15a | ||
|
|
a6a9025bab | ||
|
|
a780d4463c | ||
|
|
23539ee907 | ||
|
|
b515df611d | ||
|
|
0d896fef0b | ||
|
|
283d5c64cb | ||
|
|
3134bc432b | ||
|
|
fd93fef73e | ||
|
|
8939a9c786 | ||
|
|
52c0d360b2 | ||
|
|
d99432bff1 | ||
|
|
4dd2d3ac7d | ||
|
|
aa7fe3668c | ||
|
|
83ebcf1dae | ||
|
|
c9317659c5 | ||
|
|
6562c558de | ||
|
|
303f67c036 | ||
|
|
2482d122b8 | ||
|
|
1733c38b5c | ||
|
|
7a1e4e9e99 | ||
|
|
e590313297 | ||
|
|
b63d243e33 | ||
|
|
e9c1216d5c | ||
|
|
df9e50445e | ||
|
|
61339face6 | ||
|
|
9cd751e977 | ||
|
|
7aa08ff885 | ||
|
|
a824caf712 | ||
|
|
395210e4f0 | ||
|
|
e23354b2bb | ||
|
|
bc472eb0b3 | ||
|
|
256f8e7226 | ||
|
|
f41959537b | ||
|
|
c9e05cf9f6 | ||
|
|
82d9a02d92 | ||
|
|
dc9fa81346 | ||
|
|
b91c178200 | ||
|
|
adebdf36a5 | ||
|
|
4f34980c9f | ||
|
|
e70766a535 | ||
|
|
55110dfbac | ||
|
|
56405ac903 | ||
|
|
2b2136c468 | ||
|
|
f12031ee9e | ||
|
|
c26852da77 | ||
|
|
d9dc171c28 | ||
|
|
d407f31ae5 | ||
|
|
f688b8d299 | ||
|
|
d8e6a7b687 | ||
|
|
7c42b04eff | ||
|
|
f527841c29 | ||
|
|
48a8dc0989 | ||
|
|
7e35c9c754 | ||
|
|
7502a2b1ff | ||
|
|
6c2de40dba | ||
|
|
ef90f19eaa | ||
|
|
90ab34591a | ||
|
|
21d3a3dd1e | ||
|
|
b44e70115b | ||
|
|
ac31957707 | ||
|
|
65e27a268d | ||
|
|
6bd59b10c7 | ||
|
|
6a6a692891 | ||
|
|
479b18354d | ||
|
|
9c6452544b | ||
|
|
0a6ff900da | ||
|
|
f54f863611 | ||
|
|
9cc04da7b2 | ||
|
|
d7f5b0c9d7 | ||
|
|
9bd4598c6a | ||
|
|
e3b052bc38 | ||
|
|
f215970649 | ||
|
|
dfe1cd4f90 | ||
|
|
3d2e6aea7b | ||
|
|
749b0d7019 | ||
|
|
7978f85f7a | ||
|
|
bd14acb68a | ||
|
|
1e9ce550db | ||
|
|
6278dfa77e | ||
|
|
2a3e355437 | ||
|
|
f6b0459d27 | ||
|
|
790d6912fd | ||
|
|
e69e5b4f50 | ||
|
|
483306e73c | ||
|
|
1148a0b637 | ||
|
|
524021f0fa | ||
|
|
5b5f9aa01d | ||
|
|
f97f92c297 | ||
|
|
9d4139085b | ||
|
|
0ee7ffb5e5 | ||
|
|
8a7bb1be9f | ||
|
|
d4135e80a6 | ||
|
|
a5ade39d7c | ||
|
|
f39b4e7d22 | ||
|
|
080469cdf5 | ||
|
|
835ad29417 | ||
|
|
c09bea4710 | ||
|
|
879c0f4114 | ||
|
|
5feb07583b | ||
|
|
5388002f54 | ||
|
|
c80fa9914b | ||
|
|
b43d566968 | ||
|
|
6b4e15dd0f | ||
|
|
d9ef32d7e8 | ||
|
|
49389d6f06 | ||
|
|
c75dc3cc36 | ||
|
|
c0eabf0438 | ||
|
|
7730d0a4f8 | ||
|
|
e79da408a8 | ||
|
|
7381784d0f | ||
|
|
085fb283e5 | ||
|
|
61e0e50e7b | ||
|
|
00460d856b | ||
|
|
48958f392f | ||
|
|
a84efeb5d5 | ||
|
|
1c8c05ae04 | ||
|
|
401d386812 | ||
|
|
6b07f58e8e | ||
|
|
6e8c978d12 | ||
|
|
5b2296b056 | ||
|
|
dbf0486acb | ||
|
|
b030e935ce | ||
|
|
29bd43413a | ||
|
|
2249b9449c | ||
|
|
30920b1b78 | ||
|
|
8f92a3e875 | ||
|
|
ed1a55d9cd | ||
|
|
93ef84f495 | ||
|
|
ccfcbe8526 | ||
|
|
5938143002 | ||
|
|
8135da71bd | ||
|
|
a3c73a04c2 | ||
|
|
7f90d31846 | ||
|
|
45fbd22e28 | ||
|
|
4689d56955 | ||
|
|
aa1b2808e7 | ||
|
|
40ad4bdbd8 | ||
|
|
b6510d66e0 | ||
|
|
4ea33ea482 | ||
|
|
e13d410b4a | ||
|
|
72da7e6c54 | ||
|
|
fb05960d79 | ||
|
|
7bd0943412 | ||
|
|
bb2649d063 | ||
|
|
d743bdbf5a | ||
|
|
61890f19bc | ||
|
|
b756a8edef | ||
|
|
adcb2f1aa8 | ||
|
|
e574f4516f | ||
|
|
2ac9c11ec9 | ||
|
|
1c470ab9e3 | ||
|
|
08b8a8e3af | ||
|
|
11ee1a7dcb | ||
|
|
a281b8c74e | ||
|
|
5cb37148c6 | ||
|
|
05878d3176 | ||
|
|
d1c42262d6 | ||
|
|
8dcc114873 | ||
|
|
c54cf26848 | ||
|
|
bfb548636e | ||
|
|
36e1b2ba08 | ||
|
|
301ac279ff | ||
|
|
08d21ccba7 | ||
|
|
62876ca377 | ||
|
|
10f94148af | ||
|
|
31502c2ebc | ||
|
|
62b29ecb65 | ||
|
|
67337e013a | ||
|
|
f987fa13ea | ||
|
|
73dfe631ce | ||
|
|
83ca8147ca | ||
|
|
1c11e7f97b | ||
|
|
aefae79186 | ||
|
|
4b05a9bb6f | ||
|
|
2453719a87 | ||
|
|
ea929b00e3 | ||
|
|
ede940a398 | ||
|
|
67da853146 | ||
|
|
624befd704 | ||
|
|
203539841d | ||
|
|
262db23f7d | ||
|
|
28ea22f0e1 | ||
|
|
3f349c3531 | ||
|
|
9928e8562a | ||
|
|
b1e3fc5761 | ||
|
|
b2390f1caf | ||
|
|
8cc9aeba4a | ||
|
|
ba0823c38c | ||
|
|
b9379f2ddf | ||
|
|
38a950a6dc | ||
|
|
fb24dca019 | ||
|
|
07d131c945 | ||
|
|
0c1c710afe | ||
|
|
15cd93c30f | ||
|
|
a5d9e17a8c | ||
|
|
a82926dd0d | ||
|
|
12435b997a | ||
|
|
5945be95cf | ||
|
|
5c2e7ce407 | ||
|
|
834e894b1d | ||
|
|
d25dac69d2 | ||
|
|
3cc4173399 | ||
|
|
36ab16c1ed | ||
|
|
5356373681 | ||
|
|
f45a2643f2 | ||
|
|
e55933706d | ||
|
|
3b3d696e45 | ||
|
|
281351e6b3 | ||
|
|
34089aec70 | ||
|
|
3658d0e039 | ||
|
|
7a10636128 | ||
|
|
604ba7f4bc | ||
|
|
27b7fb54e8 | ||
|
|
d351aa842c | ||
|
|
59da705b8f | ||
|
|
99b8f16d88 | ||
|
|
06ffe722d4 | ||
|
|
6264104642 | ||
|
|
c97812c340 | ||
|
|
bd4c578230 | ||
|
|
6ec2949b6f | ||
|
|
1ff23ebfd9 | ||
|
|
7698990e37 | ||
|
|
17e092afb3 | ||
|
|
2db65b9d1f | ||
|
|
c6436f47eb | ||
|
|
e88b4a4412 | ||
|
|
01a177adfb | ||
|
|
052b5e0ea8 | ||
|
|
68cd447109 | ||
|
|
4a8a5ed8d4 | ||
|
|
84077505b0 | ||
|
|
c4554b71d3 | ||
|
|
63ce743571 | ||
|
|
6cf53c611b | ||
|
|
d8720ee325 | ||
|
|
73501f3ad3 | ||
|
|
54ee655472 | ||
|
|
571b9fb8e0 | ||
|
|
cdd6b243ff | ||
|
|
fca77a868f | ||
|
|
424e854778 | ||
|
|
0979d565bb | ||
|
|
f5e6ca3e10 | ||
|
|
2bde07561f | ||
|
|
16c92cc739 | ||
|
|
8b31a918a4 | ||
|
|
ee0bd49918 | ||
|
|
a625eeeac8 | ||
|
|
bfcd795687 | ||
|
|
e2a9be9cec | ||
|
|
37dd075309 | ||
|
|
89769fb0e5 | ||
|
|
b24fac3dd8 | ||
|
|
4794fe495c | ||
|
|
869fdbcc6a | ||
|
|
702e6d3b51 | ||
|
|
2913e13a30 | ||
|
|
5f1e37b7fa | ||
|
|
ec0209b175 | ||
|
|
a17dcbde0f | ||
|
|
fbd159a23a | ||
|
|
599a6bf050 | ||
|
|
185b16a858 | ||
|
|
e7e3ed4923 | ||
|
|
47df5476ba | ||
|
|
d7c516ab00 | ||
|
|
50838970ec | ||
|
|
1d15ee7034 | ||
|
|
7029541b4f | ||
|
|
ada8e447cc | ||
|
|
1841fc18fa | ||
|
|
94ee465682 | ||
|
|
3e021b3a75 | ||
|
|
0643f149b7 | ||
|
|
939768eec0 | ||
|
|
f2235dacdc | ||
|
|
50017c28da | ||
|
|
85b2a03a42 | ||
|
|
829087550d | ||
|
|
dd6f71fe85 | ||
|
|
92a928680c | ||
|
|
d008b1970c | ||
|
|
4affbb8c6b | ||
|
|
ddb2ea4b5f | ||
|
|
a69683183f | ||
|
|
8d34f87667 | ||
|
|
128c4fe222 | ||
|
|
b10141d71f | ||
|
|
68e0b35364 | ||
|
|
1324f5e59c | ||
|
|
7759aacb35 | ||
|
|
fd6f7cd881 | ||
|
|
3fdeb38bb7 | ||
|
|
e27f5d0460 | ||
|
|
0720128bd4 | ||
|
|
540472a093 | ||
|
|
daca78b6cd | ||
|
|
4195840b2c | ||
|
|
57c529758e | ||
|
|
5ba9a0eb3f | ||
|
|
b8888a5d46 | ||
|
|
0857f979ff | ||
|
|
11f4ae019c | ||
|
|
0ffeb0c5af | ||
|
|
d6f6b41145 | ||
|
|
64daaeb310 | ||
|
|
0646b0060e | ||
|
|
c794ca85fd | ||
|
|
5b4019dd3d | ||
|
|
a03ccd7b59 | ||
|
|
4b64aad5ce | ||
|
|
d146ff8794 | ||
|
|
5349bf7628 | ||
|
|
a79e4d1bb3 | ||
|
|
f462435dc2 | ||
|
|
48ad614441 | ||
|
|
f699516fdb | ||
|
|
ca5cbb640a | ||
|
|
0a96259ddf | ||
|
|
d99b9c04e4 | ||
|
|
64d261e053 | ||
|
|
eb027d98aa | ||
|
|
a95727b654 | ||
|
|
4e636d7eec | ||
|
|
3cd53f617a | ||
|
|
b1684e82d8 | ||
|
|
a55027b838 | ||
|
|
28678acf74 | ||
|
|
ce6594c8cc | ||
|
|
75855d5450 | ||
|
|
f248699a30 | ||
|
|
29594726ca | ||
|
|
0a41b07297 | ||
|
|
e45cb7fac1 | ||
|
|
489dbfc72a | ||
|
|
a89ae7d77a | ||
|
|
a1eeff4034 | ||
|
|
0e1013a570 | ||
|
|
4562b06a60 | ||
|
|
3c96218338 | ||
|
|
f0a4ea099c | ||
|
|
c8d6693fba | ||
|
|
81bbdfe413 | ||
|
|
0e362943bf | ||
|
|
1e37fed90b | ||
|
|
aafcfef387 | ||
|
|
4b83d8160f | ||
|
|
73a41707e5 | ||
|
|
c989c533e8 | ||
|
|
707dc8c65c | ||
|
|
4c6157a06e | ||
|
|
f973396821 | ||
|
|
e73216d4c1 | ||
|
|
e6de26736b | ||
|
|
d131addd63 | ||
|
|
0c7705beff | ||
|
|
08b11addec | ||
|
|
555f96cfaf | ||
|
|
59ffacb3df | ||
|
|
83acb66f00 | ||
|
|
1f9ae45875 | ||
|
|
ffa628be2d | ||
|
|
8916f6f829 | ||
|
|
215c8fd261 | ||
|
|
061cc908a7 | ||
|
|
18a519f9ed | ||
|
|
7970c9dbe5 | ||
|
|
5ca0c066e2 | ||
|
|
563728c7b8 | ||
|
|
31a72b6562 | ||
|
|
d3dfbc3034 | ||
|
|
f143a6ba08 | ||
|
|
28a65923b6 | ||
|
|
4ca3df77b3 | ||
|
|
4cbe264869 | ||
|
|
b6b65b6bf7 | ||
|
|
e7cc42a927 | ||
|
|
bba3ca8cc0 | ||
|
|
8423e2d245 | ||
|
|
fc263718a1 | ||
|
|
c3a99cf5a4 | ||
|
|
f6820ec615 | ||
|
|
226ad3fe22 | ||
|
|
a9b17e930c | ||
|
|
932ea7ba8f | ||
|
|
c720d78c39 | ||
|
|
8d21e441a0 | ||
|
|
16ecb1a9cb | ||
|
|
f68acca427 | ||
|
|
671d7e2beb | ||
|
|
52fc497412 | ||
|
|
2084ad318f | ||
|
|
b530c1a43d | ||
|
|
f2797a4153 | ||
|
|
659c326f89 | ||
|
|
534b07d120 | ||
|
|
de64fc8b8d | ||
|
|
1e234fcb73 | ||
|
|
fa9a7e725b | ||
|
|
95b2675f03 | ||
|
|
564902b886 | ||
|
|
071a04595a | ||
|
|
eaa4b76ede | ||
|
|
74a1713e99 | ||
|
|
5f5aa0b2f7 | ||
|
|
eef59fd40e | ||
|
|
361ff315e9 | ||
|
|
eeea8e530e | ||
|
|
8d5286703f | ||
|
|
74f2180fa4 | ||
|
|
d042169f2e | ||
|
|
1fd87bf664 | ||
|
|
eeaff6b553 | ||
|
|
6efd048fd6 | ||
|
|
6e9e694f66 | ||
|
|
44a0f1b505 | ||
|
|
9790211891 | ||
|
|
be18cc9f2d | ||
|
|
8caee09ea4 | ||
|
|
26f5305593 | ||
|
|
d33029027f | ||
|
|
339aaaec57 | ||
|
|
db2425c473 | ||
|
|
18731f6055 | ||
|
|
34f1f7a31d | ||
|
|
7ef153756b | ||
|
|
bf90509526 | ||
|
|
d853eca489 | ||
|
|
869ae01da9 | ||
|
|
d63996eea1 | ||
|
|
9bbc8eda9d | ||
|
|
9cc1b03c56 | ||
|
|
b1ab26e3ad | ||
|
|
96820418b5 | ||
|
|
385d9f16e9 | ||
|
|
d56fce37dd | ||
|
|
aef2c9e5cf | ||
|
|
89a05c580f | ||
|
|
b85e562980 | ||
|
|
a0e6628757 | ||
|
|
60a41524f0 | ||
|
|
6042395b81 | ||
|
|
8a5db8ce4b | ||
|
|
196d6e79e2 | ||
|
|
91f16215e5 | ||
|
|
9c675a7847 | ||
|
|
f9e09e87d6 | ||
|
|
73574d6293 | ||
|
|
0a5a42b32a | ||
|
|
de225205bd | ||
|
|
8a47d36480 | ||
|
|
d5f3ba8d8a | ||
|
|
782a06ce84 | ||
|
|
5cdafc50fb | ||
|
|
0ca3cdb9ae | ||
|
|
a1d6cbd5fd | ||
|
|
6c36778cac | ||
|
|
1c3a97a71a | ||
|
|
3489fe0cf4 | ||
|
|
74b6d9dff9 | ||
|
|
06ee9aa05c | ||
|
|
f0f40a8606 | ||
|
|
1f2c9879bd | ||
|
|
d1eb82bdf6 | ||
|
|
8167f623e3 | ||
|
|
9555e296a2 | ||
|
|
f460283fa1 | ||
|
|
79da8e5a37 | ||
|
|
a35ce22218 | ||
|
|
1c905da8c2 | ||
|
|
2b558768f1 | ||
|
|
7607c4c882 | ||
|
|
c9f4813ce1 | ||
|
|
0428e27039 | ||
|
|
e30da2168d | ||
|
|
0cd20768f4 | ||
|
|
ab31f34862 | ||
|
|
9a4ff5cb43 | ||
|
|
f66c91e18e | ||
|
|
a235745be7 | ||
|
|
9516da01e3 | ||
|
|
7657bd2375 | ||
|
|
0adcea9e7c | ||
|
|
aa8ad60083 | ||
|
|
5d98a86a6b | ||
|
|
4418fdaed6 | ||
|
|
c58c45c917 | ||
|
|
45eba5cabd | ||
|
|
3ab0d0d865 | ||
|
|
a6803081ab | ||
|
|
8debed805b | ||
|
|
fc9835512d | ||
|
|
5f0cab8cc2 | ||
|
|
bd391963bc | ||
|
|
97fa28fb10 | ||
|
|
67d5b39c96 | ||
|
|
dbceef2581 | ||
|
|
5b22ccfca6 | ||
|
|
714c254bab | ||
|
|
90f4db9158 | ||
|
|
eed470ddae | ||
|
|
49f72881f4 | ||
|
|
a76674032d | ||
|
|
ec392a7f9a | ||
|
|
8a2ae6c480 | ||
|
|
b3796a8e24 | ||
|
|
b9144ff987 | ||
|
|
5344949c71 | ||
|
|
05cbba9a35 | ||
|
|
325c6135cf | ||
|
|
fdea19a45b | ||
|
|
fad63c0c18 | ||
|
|
f002560616 | ||
|
|
8e7d52e645 | ||
|
|
d119594cbf | ||
|
|
84f7da6e93 | ||
|
|
22e1bafe1b | ||
|
|
3f8e42e510 | ||
|
|
9704fb04d9 | ||
|
|
dcfaf1e2b9 | ||
|
|
42f7dc1947 | ||
|
|
eb1a597456 | ||
|
|
8368815db5 | ||
|
|
9c6295d0d8 | ||
|
|
4d19f881e9 | ||
|
|
3a8820397b | ||
|
|
85b1c1fe97 | ||
|
|
a4de9e94dd | ||
|
|
0dd2c7fe24 | ||
|
|
f22c3b549e | ||
|
|
3c60b3d2c9 | ||
|
|
f2d36b84b5 | ||
|
|
9af08ef26a | ||
|
|
b4be1184fd | ||
|
|
c60e0d389c | ||
|
|
d658fe7709 | ||
|
|
54036a2b4d | ||
|
|
2da9572a45 | ||
|
|
306825aa90 | ||
|
|
c797073c05 | ||
|
|
d9d65d59d1 | ||
|
|
aad29e4487 | ||
|
|
b00985f99f | ||
|
|
538a16a5fb | ||
|
|
300a8d3a89 | ||
|
|
e3b7c5fce7 | ||
|
|
2e87ebe800 | ||
|
|
7ed9e7cdd4 | ||
|
|
7ff9c2885d | ||
|
|
18c8bbb0fc | ||
|
|
9a49fb9450 | ||
|
|
84457bc7b4 | ||
|
|
15f1e2c85c | ||
|
|
15e828e975 | ||
|
|
e4626ee52b | ||
|
|
1866c9c7ef | ||
|
|
a0f91aa814 | ||
|
|
a89c7b1a70 | ||
|
|
ded1376957 | ||
|
|
252040f03b | ||
|
|
d29abc2724 | ||
|
|
44c35d2644 | ||
|
|
f9b972349d | ||
|
|
e06cadd761 | ||
|
|
ee45d6b48f | ||
|
|
d915b280d4 | ||
|
|
39d90fe881 | ||
|
|
b9da1f18b4 | ||
|
|
69a0934173 | ||
|
|
289de85754 | ||
|
|
0ec95041d9 | ||
|
|
fcb6f78d54 | ||
|
|
29e9740668 | ||
|
|
ea8c6d5cce | ||
|
|
e4c951984a | ||
|
|
0071afb205 | ||
|
|
d3c7ac75be | ||
|
|
55d7420abf | ||
|
|
489b56456f | ||
|
|
1f24fcb364 | ||
|
|
722b31edee | ||
|
|
765f0393b0 | ||
|
|
6868ef044b | ||
|
|
5dd0622e40 | ||
|
|
48bdab1dcf | ||
|
|
aee483e9f1 | ||
|
|
8542b9bf67 | ||
|
|
feaf6f2501 | ||
|
|
d7d30aa972 | ||
|
|
91c23d1f7d | ||
|
|
57479edc59 | ||
|
|
4b462eaae9 | ||
|
|
c60fb3bc25 | ||
|
|
b17c34402d | ||
|
|
6ad71bd222 | ||
|
|
ccc08be0ee | ||
|
|
2a2fc80931 | ||
|
|
9ebca91775 | ||
|
|
456fc23463 | ||
|
|
eb17562f4d | ||
|
|
b7dbfd5cfc | ||
|
|
cdc7ab562a | ||
|
|
e6b5552cba | ||
|
|
eecf92183f | ||
|
|
11656382a7 | ||
|
|
e4d788ad0b | ||
|
|
3017442702 | ||
|
|
ba37db275c | ||
|
|
521e669879 | ||
|
|
12e302c10a | ||
|
|
7220af3ef0 | ||
|
|
42f4e0fa86 | ||
|
|
022d066fe0 | ||
|
|
4c9d7cbeed | ||
|
|
1541e382e4 | ||
|
|
1d9488d24f | ||
|
|
6cbc1afb9b | ||
|
|
3f86b660ed | ||
|
|
4603f2d9ca | ||
|
|
da818cf420 | ||
|
|
86a2ed652d | ||
|
|
269761f222 | ||
|
|
53ca3046b3 | ||
|
|
f484156d8e | ||
|
|
1da8712a4a | ||
|
|
dd47eba88c | ||
|
|
0ade8ff7a2 | ||
|
|
6a528b5fdb | ||
|
|
7f63ddc9ea | ||
|
|
d6b326c134 | ||
|
|
d944298dd7 | ||
|
|
0136ebd2b4 | ||
|
|
45f8def1ed | ||
|
|
7c6e8eeefc | ||
|
|
e81e48cde3 | ||
|
|
8eebb6ea2d | ||
|
|
ad8290ebcb | ||
|
|
15b6f6268b | ||
|
|
92d5af7446 | ||
|
|
d57425a15e | ||
|
|
a457c06500 | ||
|
|
906bbae899 | ||
|
|
e360e57a5b | ||
|
|
c5753a013c | ||
|
|
691b083364 | ||
|
|
b74a35b9d1 | ||
|
|
82269462a4 | ||
|
|
d7943aab28 | ||
|
|
446eb8e978 | ||
|
|
d91a99c833 | ||
|
|
dc00870461 | ||
|
|
b8f578862e | ||
|
|
dc24c05229 | ||
|
|
f62cf6818b | ||
|
|
c05e9da9c5 | ||
|
|
6c00194d35 | ||
|
|
6bc3f82afe | ||
|
|
12e46deea2 | ||
|
|
8608d010b8 | ||
|
|
9d4d1acf2d | ||
|
|
23087447f1 | ||
|
|
3008e4e60f | ||
|
|
9d52ed5ff6 | ||
|
|
5f047633c3 | ||
|
|
d3be1f1e2c | ||
|
|
c3b1cf7c35 | ||
|
|
682345da22 | ||
|
|
82f289c42e | ||
|
|
78eae99bd4 | ||
|
|
9ec7bb8d41 | ||
|
|
2b9bfbc309 | ||
|
|
e50d04077b | ||
|
|
b6fcaacb77 | ||
|
|
939da4e551 | ||
|
|
f30ce1f9eb | ||
|
|
343588b2a0 | ||
|
|
77b13ce9d4 | ||
|
|
7cb41d2ca9 | ||
|
|
8bbc9e6502 | ||
|
|
e29a0df3fd | ||
|
|
0eba04aac0 | ||
|
|
b78210e3be | ||
|
|
958de21be8 | ||
|
|
0eb4742982 | ||
|
|
78b1bf8f25 | ||
|
|
08f2741871 | ||
|
|
98b24ae630 | ||
|
|
7fc056c8e3 | ||
|
|
0411623857 | ||
|
|
365d71264f | ||
|
|
0d4d51fc39 | ||
|
|
b21745808b | ||
|
|
e4d5271d58 | ||
|
|
8f2f3bf75d | ||
|
|
9e96eba98f | ||
|
|
0441c83fd7 | ||
|
|
d8405052d8 | ||
|
|
cc02b07ff0 | ||
|
|
2d6aac7d6f | ||
|
|
589d43f0e5 | ||
|
|
13c1d1df7a | ||
|
|
08ade44dc8 | ||
|
|
8fb1c76247 | ||
|
|
3ad9053d65 | ||
|
|
8fe07e0f07 | ||
|
|
09b069c129 | ||
|
|
b2db083f39 | ||
|
|
53e2f3e263 | ||
|
|
945fbbc065 | ||
|
|
4dc9c7714c | ||
|
|
7302ac5871 | ||
|
|
1cfad27d6f | ||
|
|
d82fe95076 | ||
|
|
8f8df4971c | ||
|
|
fd66569950 | ||
|
|
e97d9fb0b2 | ||
|
|
04424c2a7c | ||
|
|
241e2828e7 | ||
|
|
5f6a0141f0 | ||
|
|
ef2f71859c | ||
|
|
fdaeeb5d01 | ||
|
|
e594ffe0f8 | ||
|
|
9f8c32ce8f | ||
|
|
762eb07dd4 | ||
|
|
0300458ba8 | ||
|
|
3959fcdc88 | ||
|
|
ea76c18f59 | ||
|
|
d125ecc671 | ||
|
|
7d9b90a1f3 | ||
|
|
7402c27b6a | ||
|
|
4e762e2063 | ||
|
|
0afe98b399 | ||
|
|
daed059c47 | ||
|
|
f1ce0fab8b | ||
|
|
b5d3f505e3 | ||
|
|
6c8f688f33 | ||
|
|
ed1b601a84 | ||
|
|
add541f67f | ||
|
|
bea8eb799f | ||
|
|
3cac48e86f | ||
|
|
64722da4a7 | ||
|
|
ada1e624d8 | ||
|
|
69f83cb905 | ||
|
|
807873f685 | ||
|
|
8d4be848b0 | ||
|
|
59a7c46482 | ||
|
|
eabfeb9502 | ||
|
|
291240dd94 | ||
|
|
2f6ed47168 | ||
|
|
9a73568c7a | ||
|
|
acdef87be7 | ||
|
|
b14546605d | ||
|
|
5ad46106f4 | ||
|
|
3e9be9eed3 | ||
|
|
7318a7b767 | ||
|
|
b78682f413 | ||
|
|
e50659af09 | ||
|
|
3454e5ac77 | ||
|
|
9e26aeea1d | ||
|
|
d7715b05ee | ||
|
|
db433efbef | ||
|
|
f1f8c887c6 | ||
|
|
9ae4745ca5 | ||
|
|
726d9c8ec5 | ||
|
|
a9bfa4e79b | ||
|
|
8e6bba143a | ||
|
|
feeba77f16 | ||
|
|
ea41a0e842 | ||
|
|
74b7500181 | ||
|
|
594ff8cd3d | ||
|
|
337f5f9b98 | ||
|
|
41445a1b48 | ||
|
|
269763fa0c | ||
|
|
fa90eeac55 | ||
|
|
edceffdaaf | ||
|
|
ce25fc658b | ||
|
|
b27db3e2e7 | ||
|
|
622d4214f7 | ||
|
|
0c53b5310a | ||
|
|
45ff86eae5 | ||
|
|
47316b0fb7 | ||
|
|
c09be02e4e | ||
|
|
bd59398cab | ||
|
|
8080ebceb4 | ||
|
|
1e2521c37a | ||
|
|
b744491dd2 | ||
|
|
2a089f7d90 | ||
|
|
088e3e5374 | ||
|
|
bac8a3092f | ||
|
|
e56da17957 | ||
|
|
71b2e714ee | ||
|
|
1b06afb81c | ||
|
|
819e48b03a | ||
|
|
1861c1feb6 | ||
|
|
0efccc4758 | ||
|
|
a9feeaa1c9 | ||
|
|
f9c869f521 | ||
|
|
9c766d76f3 | ||
|
|
333acccff6 | ||
|
|
1790ebf567 | ||
|
|
6354b68bae | ||
|
|
41b10fd5e4 | ||
|
|
4ad540412a | ||
|
|
8916cf273e | ||
|
|
b2923d0fc4 | ||
|
|
8fc0018cb9 | ||
|
|
d0f57efe0b | ||
|
|
595ff63b72 | ||
|
|
9990046abb | ||
|
|
8dacf72b3c | ||
|
|
2801838ffa | ||
|
|
59b34c2b3f | ||
|
|
f55f85aa14 | ||
|
|
627a80419a | ||
|
|
4bc482bc85 | ||
|
|
0a1257a23a | ||
|
|
51d99248d7 | ||
|
|
af9aa74337 | ||
|
|
321f5e615b | ||
|
|
95c31f3e17 | ||
|
|
cf69dbe1dc | ||
|
|
e92241bf97 | ||
|
|
44dc37ef6d | ||
|
|
6b0bef61a5 | ||
|
|
0c227be02d | ||
|
|
08794bad74 | ||
|
|
44693dd23a | ||
|
|
75a7be41eb | ||
|
|
913b09570c | ||
|
|
1c9b5dfd00 | ||
|
|
2954ae917b | ||
|
|
736ddaeca4 | ||
|
|
b909e32201 | ||
|
|
bcff74327b | ||
|
|
e60b63e72f | ||
|
|
2bf5d2b4e5 | ||
|
|
e1d09349ff | ||
|
|
f07c8108fc | ||
|
|
39f5078d6b | ||
|
|
1d54761d48 | ||
|
|
7cb9b2da66 | ||
|
|
b1896e3737 | ||
|
|
6fa78bdb04 | ||
|
|
ee96f7d937 | ||
|
|
129ca0e39f | ||
|
|
906703db5f | ||
|
|
0cd4a2b4ec | ||
|
|
aef8aaa0bd | ||
|
|
428fbb8622 | ||
|
|
b9f03e7d80 | ||
|
|
db686388b9 | ||
|
|
626cba4002 | ||
|
|
37d4a6b9e2 | ||
|
|
12c4561aba | ||
|
|
fed49e3718 | ||
|
|
3af37d3984 | ||
|
|
0f49a11228 | ||
|
|
27d3e165b0 | ||
|
|
e941c22f6c | ||
|
|
7281e4deb6 | ||
|
|
f2191e94b3 | ||
|
|
349ebfe4db | ||
|
|
708365c4ac | ||
|
|
0e9ea0aff1 | ||
|
|
63ba05a193 | ||
|
|
4b702815cf | ||
|
|
55e66ebcac | ||
|
|
dcd8b3699c | ||
|
|
0e2d13172a | ||
|
|
2e2556fdad | ||
|
|
859a7538e1 | ||
|
|
0d1543ee8a | ||
|
|
d3a98dd355 | ||
|
|
ad10125303 | ||
|
|
b89e866d39 | ||
|
|
afbaf1cfe0 | ||
|
|
b3be8b30e7 | ||
|
|
8bfab8f73d | ||
|
|
c6ad2c9ad2 | ||
|
|
3b44d9972e | ||
|
|
af736c98f2 | ||
|
|
f1377fa217 | ||
|
|
2ba146b9ff | ||
|
|
2361607aa3 | ||
|
|
86ffc80098 | ||
|
|
7f6915eb59 | ||
|
|
d69bcad028 | ||
|
|
4cb45e2712 | ||
|
|
b7a0ad703a | ||
|
|
7610b9a975 | ||
|
|
7d95f621df | ||
|
|
bba210e112 | ||
|
|
3a97e20bde | ||
|
|
4fe7ea00b0 | ||
|
|
3ec8ecd4de | ||
|
|
401e65e852 | ||
|
|
e7c5b691a0 | ||
|
|
9f3ea8da67 | ||
|
|
d1269b441d | ||
|
|
7615743aa5 | ||
|
|
dcaa0eeea4 | ||
|
|
1d5e2f703e | ||
|
|
4d84d624b1 | ||
|
|
633a6a0ee6 | ||
|
|
c7bcd3f438 | ||
|
|
d3a29a6f16 | ||
|
|
827711ca89 | ||
|
|
76e98f74fa | ||
|
|
fb09f4b22d | ||
|
|
bb06585748 | ||
|
|
c76ba1dcc7 | ||
|
|
a115301b04 | ||
|
|
72917117a9 | ||
|
|
6567739236 | ||
|
|
4aa6b47c0e | ||
|
|
03558b012c | ||
|
|
3288efdad6 | ||
|
|
3902a343f3 | ||
|
|
882b7d0391 | ||
|
|
81f082825d | ||
|
|
392fd6fed3 | ||
|
|
51afed4041 | ||
|
|
17e3b71d9c | ||
|
|
6e75089f3a | ||
|
|
6dc640b129 | ||
|
|
27cbaac343 | ||
|
|
fa4006619e | ||
|
|
cb8fe8462a | ||
|
|
abd51a5511 | ||
|
|
a0cc1e6b0c | ||
|
|
50399c6bfa | ||
|
|
de48c1be44 | ||
|
|
0786ec4b66 | ||
|
|
db319e0ebc | ||
|
|
4fc568856a | ||
|
|
4c6771669b | ||
|
|
9bca2a91c9 | ||
|
|
66eaaf5a48 | ||
|
|
9837f0e2e1 | ||
|
|
6b8ffb4c68 | ||
|
|
f35dd34da9 | ||
|
|
ed19e4fa08 | ||
|
|
661e1a4f90 | ||
|
|
5826de76ca | ||
|
|
05888740e5 | ||
|
|
41f3b0c333 | ||
|
|
70f3e72a20 | ||
|
|
e873afd40b | ||
|
|
2777c2937a | ||
|
|
798903e4cc | ||
|
|
58622ba18f | ||
|
|
c368dcd5b7 | ||
|
|
0b4c652ce7 | ||
|
|
dbaacc411a | ||
|
|
1850185d1e | ||
|
|
2e9d445d36 | ||
|
|
aed89d82fb | ||
|
|
231adac6d8 | ||
|
|
587c4e5915 | ||
|
|
55f1cbf18f | ||
|
|
38168a545b | ||
|
|
43c6df49d7 | ||
|
|
f1c59faf72 | ||
|
|
5f7019325c | ||
|
|
fe4dae8518 | ||
|
|
1f848b205b | ||
|
|
742c470d81 | ||
|
|
5ead3342cc | ||
|
|
b95dc2ecce | ||
|
|
4d0950215f | ||
|
|
da0ce9fe0d | ||
|
|
ca62e720b5 | ||
|
|
c4b1795396 | ||
|
|
fd2e47ed73 | ||
|
|
d5f2255a68 | ||
|
|
05b58e9263 | ||
|
|
4a91c27e4b | ||
|
|
3a03d46d8d | ||
|
|
f03aff7006 | ||
|
|
043b8a3105 | ||
|
|
1dd9984521 | ||
|
|
d2be7f8c8f | ||
|
|
88dc202db2 | ||
|
|
083d54b008 | ||
|
|
87d77efa57 | ||
|
|
35c4a41d7b | ||
|
|
1ca3ca07d5 | ||
|
|
d673846e3d | ||
|
|
f62b7afede | ||
|
|
e65770a53a | ||
|
|
a92a741932 | ||
|
|
45f67191ba | ||
|
|
93f5da325b | ||
|
|
8fb955e182 | ||
|
|
9f5e6a4b37 | ||
|
|
f43738446e | ||
|
|
923a46d304 | ||
|
|
b9b5eaccae | ||
|
|
cda11491c2 | ||
|
|
98c539f662 | ||
|
|
9fb958b302 | ||
|
|
8e25e76439 | ||
|
|
62694da7e6 | ||
|
|
86064651af | ||
|
|
65daaeb617 | ||
|
|
08b39f50b3 | ||
|
|
d0f7e5ca4d | ||
|
|
4eb5058e68 | ||
|
|
1054193298 | ||
|
|
38c6cf0450 | ||
|
|
5b04b86867 | ||
|
|
a074bcfd56 | ||
|
|
f93179d946 | ||
|
|
2c347bc092 | ||
|
|
0f7119f468 | ||
|
|
2685a24705 | ||
|
|
371f72f4f1 | ||
|
|
c70c00043b | ||
|
|
50d0a88276 | ||
|
|
5bbf576dae | ||
|
|
5d334e9619 | ||
|
|
98f9353338 | ||
|
|
d3de7037e5 | ||
|
|
64431c6711 | ||
|
|
d4ce193dc8 | ||
|
|
606305aec4 | ||
|
|
a95f44d68b | ||
|
|
ef2dc4b9e1 | ||
|
|
9baca1772b | ||
|
|
04cd19349d | ||
|
|
1280e5bc8b | ||
|
|
dda90f956d | ||
|
|
bc4b599513 | ||
|
|
090d52d678 | ||
|
|
a47ad4842a | ||
|
|
3d5ed840dc | ||
|
|
11d75ff581 | ||
|
|
306fb7a3d1 | ||
|
|
0839b6f58e | ||
|
|
fceca703b3 | ||
|
|
4dc60d2477 | ||
|
|
d840d0b67d | ||
|
|
43dad4c465 | ||
|
|
60812b2d8a | ||
|
|
35e2caff13 | ||
|
|
1d9d5c6bc7 | ||
|
|
4d99536ea7 | ||
|
|
34537180c3 | ||
|
|
cb01920ee6 | ||
|
|
437b01a0ff | ||
|
|
075a2abf71 | ||
|
|
985875cc75 | ||
|
|
1c45bc615f | ||
|
|
fa7f2606fb | ||
|
|
12b95f1c72 | ||
|
|
a0aee2021d | ||
|
|
c90fd1e6d8 | ||
|
|
3b769fd2de | ||
|
|
71ecb89abc | ||
|
|
7b6bc1d3bc | ||
|
|
9c3be40fbe | ||
|
|
ab87fa9ce4 | ||
|
|
d1940a023a | ||
|
|
5a176a037c | ||
|
|
ec25191c98 | ||
|
|
20b321f928 | ||
|
|
425b016d63 | ||
|
|
b2c7189ce4 | ||
|
|
f66886dbdb | ||
|
|
712f8b4680 | ||
|
|
f626ee060a | ||
|
|
86aa7c97be | ||
|
|
30e3525987 | ||
|
|
ad44f838da | ||
|
|
2569a35b6c | ||
|
|
1ee5e50d50 | ||
|
|
1dbec5eca8 | ||
|
|
2bc8db308c | ||
|
|
f196740426 | ||
|
|
20121b79c5 | ||
|
|
741a4cfe53 | ||
|
|
0343de9f34 | ||
|
|
4772bca14a | ||
|
|
6ae1a5ba0d | ||
|
|
217c9718e4 | ||
|
|
61d7893467 | ||
|
|
8f26c01f4b | ||
|
|
61045ddd7f | ||
|
|
1bf72a0bc3 | ||
|
|
6d84b1bb8d | ||
|
|
8abd0b1fdf | ||
|
|
81e125b7ba | ||
|
|
d5e1468718 | ||
|
|
c232bf5ed6 | ||
|
|
21b25ffaee | ||
|
|
ca0a93df08 | ||
|
|
699a22c757 | ||
|
|
8b2b1669b5 | ||
|
|
c1e8370916 | ||
|
|
ddedea8b90 | ||
|
|
8f414ce458 | ||
|
|
453f23da20 | ||
|
|
9e91e42a1b | ||
|
|
b666734c79 | ||
|
|
a2297f303d | ||
|
|
ecde942255 | ||
|
|
d668d43a0a | ||
|
|
ca91a5dd95 | ||
|
|
5f9780d71c | ||
|
|
ee37464741 | ||
|
|
8d73f927db | ||
|
|
4a0222bd1c | ||
|
|
c0b8f5e3e1 | ||
|
|
a9a0b263dc | ||
|
|
f2b73187d8 | ||
|
|
ef10ade0cc | ||
|
|
719bb4263e | ||
|
|
b3602b268e | ||
|
|
66ec9bae27 | ||
|
|
d96b6e77c0 | ||
|
|
da64c018ac | ||
|
|
f9fb97adf2 | ||
|
|
8316bc6480 | ||
|
|
08021e039a | ||
|
|
cc6e0937a0 | ||
|
|
c1d694a97c | ||
|
|
fcf4f40c36 | ||
|
|
380b03399c | ||
|
|
c33d02c53f | ||
|
|
fa5e37993e | ||
|
|
437b2d506b | ||
|
|
4ed09f6431 | ||
|
|
0b98a6acf8 | ||
|
|
1d73c86cb2 | ||
|
|
40fe0f3239 | ||
|
|
d1ea689999 | ||
|
|
a6644ad5ff | ||
|
|
658746d2a3 | ||
|
|
cbdd4de630 | ||
|
|
00c612485b | ||
|
|
3a6192bf73 | ||
|
|
c64b5c2850 | ||
|
|
fdbf079896 | ||
|
|
d1a5395727 | ||
|
|
83a3642c0e | ||
|
|
9932d34304 | ||
|
|
7aa37a1976 | ||
|
|
fa42fbdab8 | ||
|
|
caa83ac830 | ||
|
|
3963fa9738 | ||
|
|
005a98d020 | ||
|
|
9560dc9408 | ||
|
|
4ac9a5edf0 | ||
|
|
37e62597ae | ||
|
|
90bfe378d0 | ||
|
|
ce22b494ec | ||
|
|
f9e0420647 | ||
|
|
2fe568d9ba | ||
|
|
2d4979df4d | ||
|
|
b555b014b8 | ||
|
|
999b888c54 | ||
|
|
5193d7bddb | ||
|
|
6b03379e4e | ||
|
|
08d687ad60 | ||
|
|
eb57089f06 | ||
|
|
a76e4fede1 | ||
|
|
705d043540 | ||
|
|
5462e251f8 | ||
|
|
50788af6ca | ||
|
|
1a07c5a329 | ||
|
|
9fb81b2814 | ||
|
|
10ad7fbf6e | ||
|
|
811c4f2630 | ||
|
|
f7e3b0a64f | ||
|
|
7d83d76fb3 | ||
|
|
d3c41b38f7 | ||
|
|
57d6b16d5c | ||
|
|
27aa5ae7db | ||
|
|
62e8f564b9 | ||
|
|
a1d7bb4208 | ||
|
|
5d8dae05c4 | ||
|
|
6bde07b5a0 | ||
|
|
846ab08661 | ||
|
|
999cf66b27 | ||
|
|
60539d890b | ||
|
|
e5a0f25d94 | ||
|
|
fde9d40098 | ||
|
|
f70e9ea076 | ||
|
|
d0af4aac4d | ||
|
|
7de3704210 | ||
|
|
1c33b837b8 | ||
|
|
95d20d7fba | ||
|
|
90b8806e7c | ||
|
|
39df80bf99 | ||
|
|
bac4beae03 | ||
|
|
a7b68c18b5 | ||
|
|
ef2360baee | ||
|
|
8716e7e601 | ||
|
|
00c2dae969 | ||
|
|
8782bc5896 | ||
|
|
6359b90352 | ||
|
|
6cfa4976fe | ||
|
|
35cd7cf2b8 | ||
|
|
b2d7f079b7 | ||
|
|
726069bc4b | ||
|
|
fbccf01933 | ||
|
|
c9f3c6f4a3 | ||
|
|
c47da013ff | ||
|
|
bc76499957 | ||
|
|
24afcff0ea | ||
|
|
6777f24845 | ||
|
|
61e0923fc4 | ||
|
|
757caeb9a4 | ||
|
|
fe9dc6b272 | ||
|
|
24efdbe4da | ||
|
|
9ca102cf81 | ||
|
|
48df31d7b7 | ||
|
|
539afb1e1d | ||
|
|
bdcba44ca5 | ||
|
|
99a51b07ac | ||
|
|
5fbaca75b4 | ||
|
|
a6974371b0 | ||
|
|
409fa49234 | ||
|
|
9bbd6a70b8 | ||
|
|
f0b4cb608a | ||
|
|
284d7e26d1 | ||
|
|
965c0937ac | ||
|
|
d9cf6a4431 | ||
|
|
e6ed8ee509 | ||
|
|
2563649b3e | ||
|
|
203bc41b06 | ||
|
|
cb1d18c7c8 | ||
|
|
5ea68dafc4 | ||
|
|
68bb8252af | ||
|
|
406f742d29 | ||
|
|
5522e57f65 | ||
|
|
94e27dbfc5 | ||
|
|
f5fc8f763f | ||
|
|
9058bf615c | ||
|
|
9d17137dec | ||
|
|
4a318553f7 | ||
|
|
0017074d38 | ||
|
|
a39a26fcc2 | ||
|
|
2fe859b111 | ||
|
|
c105b2df37 | ||
|
|
4fb86ab55a | ||
|
|
1ed98a5963 | ||
|
|
a4a29ceb3c | ||
|
|
6249083431 | ||
|
|
aa18c65fa8 | ||
|
|
a7900940da | ||
|
|
008bc98070 | ||
|
|
0705589cc2 | ||
|
|
b2caba593f | ||
|
|
6d4c64fcd5 | ||
|
|
7d41781fb4 | ||
|
|
0760facb77 | ||
|
|
a0ce095807 | ||
|
|
df0110913a | ||
|
|
06731374a4 | ||
|
|
6c8b7c0082 | ||
|
|
93136961b9 | ||
|
|
529a691e1d | ||
|
|
9f0b3eba47 | ||
|
|
6d897793cb | ||
|
|
0e12fc6b02 | ||
|
|
c00558ea1b | ||
|
|
bacf27a3ca | ||
|
|
6560ea0630 | ||
|
|
8338231ce5 | ||
|
|
5813a91244 | ||
|
|
dfd8ab3545 | ||
|
|
a5b9b949a8 | ||
|
|
72570e4510 | ||
|
|
3002aead6b | ||
|
|
841edbe6fb | ||
|
|
2b9aca0c56 | ||
|
|
3a17c3ee6d | ||
|
|
df09252ee0 | ||
|
|
f51778d417 | ||
|
|
119fc63794 | ||
|
|
ee3425d3be | ||
|
|
31c979f30f | ||
|
|
89f2c26cd6 | ||
|
|
0dbf43d0aa | ||
|
|
8a6d11b191 | ||
|
|
d8eb926e2c | ||
|
|
21d0adbdae | ||
|
|
c5fd3a5753 | ||
|
|
eae4e3d983 | ||
|
|
bb1fe8daef | ||
|
|
c5d8e09b41 | ||
|
|
369eae3d92 | ||
|
|
e2fa457ca2 | ||
|
|
871f764e98 | ||
|
|
f8853af902 | ||
|
|
4248d20f39 | ||
|
|
5cda08e7b0 | ||
|
|
7e9d96ee87 | ||
|
|
974ac31d33 | ||
|
|
0658b70631 | ||
|
|
53258eeede | ||
|
|
4f174308b9 | ||
|
|
98c9e40349 | ||
|
|
3a4756bd83 | ||
|
|
6ccb05cb2c | ||
|
|
3c2d32b867 | ||
|
|
956050434f | ||
|
|
38ab1550d2 | ||
|
|
e852d1e57c | ||
|
|
7de0216976 | ||
|
|
911288e695 | ||
|
|
72e1f20383 | ||
|
|
d28a6eaf9d | ||
|
|
17f3366556 | ||
|
|
0bef04ae0a | ||
|
|
f11b906fd9 | ||
|
|
518358d9dc | ||
|
|
5ffde21d83 | ||
|
|
052302b3e7 | ||
|
|
fe1ce21114 | ||
|
|
ce5c9da107 | ||
|
|
cf25b2866e | ||
|
|
07fd5a5f5f | ||
|
|
913fdac671 | ||
|
|
7dc838dea6 | ||
|
|
7112fd2a22 | ||
|
|
56e8c143dd | ||
|
|
7b4cbbe816 | ||
|
|
8f8ee4662d | ||
|
|
86013c7db4 | ||
|
|
01aa3324f8 | ||
|
|
b9cfeee965 | ||
|
|
b3684a70b5 | ||
|
|
51fce9343b | ||
|
|
0c5c3448d0 | ||
|
|
deaef3ab86 | ||
|
|
a443491c0c | ||
|
|
276d893198 | ||
|
|
653d0e71e4 | ||
|
|
faa7d948a7 | ||
|
|
771342989e | ||
|
|
e9ce519e4b | ||
|
|
c016b102eb | ||
|
|
0583c60837 | ||
|
|
1c1a85dcef | ||
|
|
c71e76335b | ||
|
|
c1a32c4eb9 | ||
|
|
9ad1f769d3 | ||
|
|
87f8fd34b8 | ||
|
|
e206d2919e | ||
|
|
9d809aa2ba | ||
|
|
8f744794e4 | ||
|
|
78ab4217be | ||
|
|
d090df94c5 | ||
|
|
937f26da41 | ||
|
|
98e3ff014e | ||
|
|
6f84526364 | ||
|
|
105d38c885 | ||
|
|
d7bdcd69fc | ||
|
|
87e537da90 | ||
|
|
8f16695f06 | ||
|
|
8403f6291f | ||
|
|
5af6ac3e80 | ||
|
|
0d557094b2 | ||
|
|
a2aa78afd4 | ||
|
|
b0de8abb63 | ||
|
|
6ff540ed08 | ||
|
|
2b8ed9850b | ||
|
|
dcd579b5e3 | ||
|
|
c9d2d301aa | ||
|
|
1aaef598a5 | ||
|
|
73d1f3d0e8 | ||
|
|
e369ded6c5 | ||
|
|
269846c587 | ||
|
|
8dc98420db | ||
|
|
1014abe92f | ||
|
|
6927f10f8f | ||
|
|
49d3a7190a | ||
|
|
a2e65b0018 | ||
|
|
f48ee01a03 | ||
|
|
0e926c566b | ||
|
|
d2c4c2c34c | ||
|
|
c2253d1e25 | ||
|
|
eae16b6e8c | ||
|
|
868ae5b5dd | ||
|
|
1406503e10 | ||
|
|
9ca9d88546 | ||
|
|
203d3f672c | ||
|
|
698b2688f6 | ||
|
|
be1620dd07 | ||
|
|
e1f0969957 | ||
|
|
e1dd8cf2ab | ||
|
|
8ee90777ee | ||
|
|
2fe9c1e55f | ||
|
|
9dd7e2e43d | ||
|
|
5efbdf5d04 | ||
|
|
5be3472413 | ||
|
|
a9a0953653 | ||
|
|
d4ac4c44d0 | ||
|
|
f459ff8ad0 | ||
|
|
b96ea36b70 | ||
|
|
e543cc0fed | ||
|
|
0b1b4df210 | ||
|
|
e59ffb0b19 | ||
|
|
120c8f2c28 | ||
|
|
fbfc5c8a2d | ||
|
|
31b018c80e | ||
|
|
255d708fb6 | ||
|
|
78d1da5fbc | ||
|
|
9c22a770ef | ||
|
|
b201828236 | ||
|
|
2a8a885271 | ||
|
|
7adefd6ee0 | ||
|
|
f967a5ecdc | ||
|
|
c8d6dc2531 | ||
|
|
216b5fba7a | ||
|
|
f4ec9a72d5 | ||
|
|
cf0c2825eb | ||
|
|
be46c419dc | ||
|
|
62c68d06fe | ||
|
|
4f4d8419bc | ||
|
|
16e17954b4 | ||
|
|
cc1d080a5a | ||
|
|
dd7f4f6752 | ||
|
|
9daeba02b5 | ||
|
|
8a96dbd121 | ||
|
|
2a57d0b6d0 | ||
|
|
8a5c1bade5 | ||
|
|
fcc6943f98 | ||
|
|
72a9de058d | ||
|
|
8748be28b7 | ||
|
|
20bdea7ae0 | ||
|
|
e19b8d35a9 | ||
|
|
81df74dfc8 | ||
|
|
153fa7478f | ||
|
|
500e9a4010 | ||
|
|
5352fc87ee | ||
|
|
f07fd64ffb | ||
|
|
36f299c031 | ||
|
|
78cf310c58 | ||
|
|
18a3d71024 | ||
|
|
26db1048f9 | ||
|
|
b61a250d58 | ||
|
|
1d10eba0cc | ||
|
|
35042132fa | ||
|
|
57c049b49f | ||
|
|
7a0ce0c957 | ||
|
|
eb4cadb0b5 | ||
|
|
ac0ca083c0 | ||
|
|
9afb4a9315 | ||
|
|
df065e94b7 | ||
|
|
a9789697e7 | ||
|
|
2d91be8814 | ||
|
|
5c58b0c2f4 | ||
|
|
f0139f9808 | ||
|
|
5610a3184e | ||
|
|
b202c73708 | ||
|
|
dd4cec84bf | ||
|
|
a1dac1e290 | ||
|
|
e199d6725e | ||
|
|
aef38b945d | ||
|
|
cd5eac2cbb | ||
|
|
8fea443e71 | ||
|
|
2a47951e46 | ||
|
|
5234fda266 | ||
|
|
e63067cd9f | ||
|
|
be61cf6a88 | ||
|
|
5efc89d514 | ||
|
|
9c104faff3 | ||
|
|
cf8fe16b09 | ||
|
|
71db193675 | ||
|
|
9952d9451e | ||
|
|
fb738ad9fa | ||
|
|
46f2f752b0 | ||
|
|
42730b8fce | ||
|
|
1c2be579d9 | ||
|
|
51e891ff88 | ||
|
|
731efe1c01 | ||
|
|
f77dd06e65 | ||
|
|
af20f3df64 | ||
|
|
4078212089 | ||
|
|
7bdb3181e2 | ||
|
|
933608aec1 | ||
|
|
1d7f06bbba | ||
|
|
f26cadab7f | ||
|
|
eacf41a4f6 | ||
|
|
ab3e64271b | ||
|
|
e26ea14104 | ||
|
|
3967e23828 | ||
|
|
1f8c6f87c9 | ||
|
|
f6203fe60a | ||
|
|
06bf710515 | ||
|
|
0f3cc3196c | ||
|
|
4403a00651 | ||
|
|
9c46feb22b | ||
|
|
10277aa956 | ||
|
|
ff093d98c6 | ||
|
|
acc0e0875b | ||
|
|
69f85bd688 | ||
|
|
910d983b82 | ||
|
|
128415bc9e | ||
|
|
082ce798d8 | ||
|
|
234abd82a2 | ||
|
|
3cbc1bbb1b | ||
|
|
0ed9f6cc4f | ||
|
|
10b092a9a7 | ||
|
|
444a897410 | ||
|
|
e013afb053 | ||
|
|
34364f5627 | ||
|
|
cef378d820 | ||
|
|
a27353c166 | ||
|
|
bbd197c71d | ||
|
|
fabf013714 | ||
|
|
81dcfecb4e | ||
|
|
971a62ebc9 | ||
|
|
04f2c92ba6 | ||
|
|
00d0f8cfc7 | ||
|
|
c5c404ea05 | ||
|
|
c80a44933c | ||
|
|
5599b999ec | ||
|
|
172cbdaa84 | ||
|
|
a3c4f12764 | ||
|
|
bf1cd457cd | ||
|
|
8af50a51ba | ||
|
|
ddf31e87b2 | ||
|
|
5adbc012f3 | ||
|
|
393fc349b9 | ||
|
|
dfed4963ed | ||
|
|
131adb6f4e | ||
|
|
a8b3cbb683 | ||
|
|
e97d5c7354 | ||
|
|
061c44f958 | ||
|
|
f5d8433341 | ||
|
|
f78a71e8ed | ||
|
|
4d48c72146 | ||
|
|
71ff828947 | ||
|
|
b6245bdef7 | ||
|
|
ce1cd1ab9c | ||
|
|
54b0debb3b | ||
|
|
1876b56022 | ||
|
|
d148a23ed6 | ||
|
|
049a5f1be6 | ||
|
|
f3880b7601 | ||
|
|
fbb45a8961 | ||
|
|
b8c460b825 | ||
|
|
63191bc641 | ||
|
|
9f012c261a | ||
|
|
dc7701ad70 | ||
|
|
e8666827e6 | ||
|
|
5e2c51a741 | ||
|
|
51421ce657 | ||
|
|
339e6039e1 | ||
|
|
43054906dc | ||
|
|
57dedcf816 | ||
|
|
4dc21c43fa | ||
|
|
1c86908b90 | ||
|
|
9b26973883 | ||
|
|
2585e983a5 | ||
|
|
a808cb44c2 | ||
|
|
edd9d2cb9c | ||
|
|
33d24f79ce | ||
|
|
cc095e4edf | ||
|
|
07641d57ab | ||
|
|
5643c51507 | ||
|
|
ad6254c0b8 | ||
|
|
d6ca421d59 | ||
|
|
79bd01f810 | ||
|
|
e357352240 | ||
|
|
0accfade02 | ||
|
|
00b7afe3ae | ||
|
|
2e76541fa5 | ||
|
|
e2911078e3 | ||
|
|
c6157687c9 | ||
|
|
87a1818486 | ||
|
|
d6c5d91e2c |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/packages/node_modules/** linguist-generated=false
|
||||
34
.github/ISSUE_TEMPLATE.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<!--
|
||||
## Before you hit that Submit button....
|
||||
|
||||
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
|
||||
|
||||
If your issue is:
|
||||
- a general 'how-to' type question,
|
||||
- a feature request or suggestion for a change,
|
||||
- or problems with 3rd party (`node-red-contrib-`) nodes
|
||||
|
||||
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
||||
|
||||
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
|
||||
|
||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
|
||||
|
||||
## So you have a real issue to raise...
|
||||
|
||||
To help us understand the issue, please fill-in as much of the following information as you can:
|
||||
-->
|
||||
|
||||
### What are the steps to reproduce?
|
||||
|
||||
### What happens?
|
||||
|
||||
### What do you expect to happen?
|
||||
|
||||
### Please tell us about your environment:
|
||||
|
||||
- [ ] Node-RED version:
|
||||
- [ ] node.js version:
|
||||
- [ ] npm version:
|
||||
- [ ] Platform/OS:
|
||||
- [ ] Browser:
|
||||
34
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
34
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<!--
|
||||
## Before you hit that Submit button....
|
||||
|
||||
Please read our [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
|
||||
before submitting a pull-request.
|
||||
|
||||
## Types of changes
|
||||
|
||||
What types of changes does your code introduce?
|
||||
Put an `x` in the boxes that apply
|
||||
-->
|
||||
|
||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
|
||||
<!--
|
||||
If you want to raise a pull-request with a new feature, or a refactoring
|
||||
of existing code, it **may well get rejected** if it hasn't been discussed on
|
||||
the [forum](https://discourse.nodered.org) or
|
||||
[slack team](https://nodered.org/slack) first.
|
||||
|
||||
-->
|
||||
|
||||
## Proposed changes
|
||||
|
||||
<!-- Describe the nature of this change. What problem does it address? -->
|
||||
|
||||
## Checklist
|
||||
<!-- Put an `x` in the boxes that apply -->
|
||||
|
||||
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
|
||||
- [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team.
|
||||
- [ ] I have run `grunt` to verify the unit tests pass
|
||||
- [ ] I have added suitable unit tests to cover the new/changed functionality
|
||||
28
.gitignore
vendored
28
.gitignore
vendored
@@ -1,10 +1,24 @@
|
||||
settings.js
|
||||
node_modules
|
||||
.DS_store
|
||||
.config.json
|
||||
.dist
|
||||
.jshintignore
|
||||
.npm
|
||||
.project
|
||||
.sessions.json
|
||||
.settings
|
||||
.tern-project
|
||||
*.backup
|
||||
*_cred*
|
||||
coverage
|
||||
credentials.json
|
||||
flows*.json
|
||||
flows.backup
|
||||
*_cred*
|
||||
nodes/node-red-nodes/
|
||||
.npm
|
||||
/coverage
|
||||
.config.json
|
||||
node_modules
|
||||
public
|
||||
locales/zz-ZZ
|
||||
nodes/core/locales/zz-ZZ
|
||||
!packages/node_modules
|
||||
packages/node_modules/@node-red/editor-client/public
|
||||
!test/**/node_modules
|
||||
docs
|
||||
!packages/node_modules/**/docs
|
||||
|
||||
19
.jshintrc
Normal file
19
.jshintrc
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"asi": true, // allow missing semicolons
|
||||
"curly": true, // require braces
|
||||
"eqnull": true, // ignore ==null
|
||||
//"eqeqeq": true, // enforce ===
|
||||
"freeze": true, // don't allow override
|
||||
"indent": 4, // default indent of 4
|
||||
"forin": true, // require property filtering in "for in" loops
|
||||
"immed": true, // require immediate functions to be wrapped in ( )
|
||||
"nonbsp": true, // warn on unexpected whitespace breaking chars
|
||||
//"strict": true, // commented out for now as it causes 100s of warnings, but want to get there eventually
|
||||
//"unused": true, // Check for unused functions and variables
|
||||
"loopfunc": true, // allow functions to be defined in loops
|
||||
//"expr": true, // allow ternery operator syntax...
|
||||
"shadow": true, // allow variable shadowing (re-use of names...)
|
||||
"sub": true, // don't warn that foo['bar'] should be written as foo.bar
|
||||
"proto": true, // allow setting of __proto__ in node < v0.12,
|
||||
"esversion": 6 // allow es6
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
.git/*
|
||||
*.json
|
||||
lib/*
|
||||
/Gruntfile.js
|
||||
/.git/*
|
||||
*.backup
|
||||
/public/*
|
||||
|
||||
19
.travis.yml
19
.travis.yml
@@ -1,11 +1,10 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
before_install:
|
||||
- npm install -g npm@~1.4.18
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.8"
|
||||
script:
|
||||
- istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage
|
||||
before_script:
|
||||
- npm install -g istanbul
|
||||
- npm install coveralls
|
||||
matrix:
|
||||
include:
|
||||
- node_js: "10"
|
||||
script:
|
||||
- ./node_modules/.bin/grunt && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage
|
||||
before_script:
|
||||
- npm install -g istanbul coveralls
|
||||
- node_js: "8"
|
||||
|
||||
1499
CHANGELOG.md
Normal file
1499
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
74
CODE_OF_CONDUCT.md
Normal file
74
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at team@nodered.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -6,11 +6,14 @@ We welcome contributions, but request you follow these guidelines.
|
||||
- [Feature requests](#feature-requests)
|
||||
- [Pull-Requests](#pull-requests)
|
||||
- [Contributor License Agreement](#contributor-license-agreement)
|
||||
|
||||
|
||||
This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable
|
||||
behavior to the project's core team at team@nodered.org.
|
||||
|
||||
## Raising issues
|
||||
|
||||
Please raise any bug reports on the project's
|
||||
[issue tracker](https://github.com/node-red/node-red/issues?state=open). Be sure to
|
||||
Please raise any bug reports on the relevant project's issue tracker. Be sure to
|
||||
search the list to see if your issue has already been raised.
|
||||
|
||||
A good bug report is one that make it easy for us to understand what you were
|
||||
@@ -25,32 +28,23 @@ At a minimum, please include:
|
||||
- Version of Node-RED - either release number if you downloaded a zip, or the first few lines of `git log` if you are cloning the repository directly.
|
||||
- Version of node.js - what does `node -v` say?
|
||||
|
||||
|
||||
## Feature requests
|
||||
|
||||
For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red).
|
||||
For feature requests, please raise them on the [forum](https://discourse.nodered.org).
|
||||
|
||||
## Pull-Requests
|
||||
|
||||
If you want to raise a pull-request with a new feature, or a refactoring
|
||||
of existing code, it may well get rejected if you haven't discussed it on
|
||||
the [mailing list](https://groups.google.com/forum/#!forum/node-red) first.
|
||||
of existing code, it may well get rejected if you haven't discussed it on
|
||||
the [forum](https://discourse.nodered.org) first.
|
||||
|
||||
### Contributor License Agreement
|
||||
All contributors need to sign the JS Foundation's Contributor License Agreement.
|
||||
It is an online process and quick to do. You can read the details of the agreement
|
||||
here: https://cla.js.foundation/node-red/node-red.
|
||||
|
||||
In order for us to accept pull-requests, the contributor must first complete
|
||||
a Contributor License Agreement (CLA). This clarifies the intellectual
|
||||
property license granted with any contribution. It is for your protection as a
|
||||
Contributor as well as the protection of IBM and its customers; it does not
|
||||
change your rights to use your own Contributions for any other purpose.
|
||||
If you raise a pull-request without having signed the CLA, you will be prompted
|
||||
to do so automatically.
|
||||
|
||||
You can download the CLAs here:
|
||||
|
||||
- [individual](http://nodered.org/cla/node-red-cla-individual.pdf)
|
||||
- [corporate](http://nodered.org/cla/node-red-cla-corporate.pdf)
|
||||
|
||||
If you are an IBMer, please contact us directly as the contribution process is
|
||||
slightly different.
|
||||
|
||||
### Coding standards
|
||||
|
||||
@@ -59,13 +53,5 @@ code base. Some basic rules include:
|
||||
|
||||
- all files must have the Apache license in the header.
|
||||
- indent with 4-spaces, no tabs. No arguments.
|
||||
- opening brace on same line as `if`/`for`/`function`/etc, closing brace on its
|
||||
own line.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- opening brace on same line as `if`/`for`/`function` and so on, closing brace
|
||||
on its own line.
|
||||
|
||||
650
Gruntfile.js
650
Gruntfile.js
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,78 +14,592 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var path = require("path");
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Project configuration.
|
||||
|
||||
var nodemonArgs = ["-v"];
|
||||
var flowFile = grunt.option('flowFile');
|
||||
if (flowFile) {
|
||||
nodemonArgs.push(flowFile);
|
||||
}
|
||||
|
||||
var nonHeadless = grunt.option('non-headless');
|
||||
if (nonHeadless) {
|
||||
process.env.NODE_RED_NON_HEADLESS = 'true';
|
||||
}
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
simplemocha: {
|
||||
options: {
|
||||
globals: ['expect'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
ui: 'bdd',
|
||||
reporter: 'spec'
|
||||
},
|
||||
all: { src: ['test/**/*_spec.js'] },
|
||||
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
|
||||
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
paths: {
|
||||
dist: ".dist"
|
||||
},
|
||||
simplemocha: {
|
||||
options: {
|
||||
globals: ['expect'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
ui: 'bdd',
|
||||
reporter: 'spec'
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
// http://www.jshint.com/docs/options/
|
||||
"asi": true, // allow missing semicolons
|
||||
"curly": true, // require braces
|
||||
"eqnull": true, // ignore ==null
|
||||
"forin": true, // require property filtering in "for in" loops
|
||||
"immed": true, // require immediate functions to be wrapped in ( )
|
||||
"nonbsp": true, // warn on unexpected whitespace breaking chars
|
||||
//"strict": true, // commented out for now as it causes 100s of warnings, but want to get there eventually
|
||||
"loopfunc": true, // allow functions to be defined in loops
|
||||
"sub": true // don't warn that foo['bar'] should be written as foo.bar
|
||||
},
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'red.js',
|
||||
'red/**/*.js',
|
||||
'nodes/**/*.js',
|
||||
'public/red/**/*.js'
|
||||
],
|
||||
|
||||
core: {
|
||||
files: {
|
||||
src: [
|
||||
'Gruntfile.js',
|
||||
'red.js',
|
||||
'red/**/*.js'
|
||||
]
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
files: {
|
||||
src: [ 'nodes/**/*.js' ]
|
||||
}
|
||||
},
|
||||
editor: {
|
||||
files: {
|
||||
src: [ 'public/red/**/*.js' ]
|
||||
}
|
||||
},
|
||||
tests: {
|
||||
files: {
|
||||
src: ['test/**/*.js']
|
||||
},
|
||||
options: {
|
||||
"expr": true
|
||||
}
|
||||
}
|
||||
|
||||
all: { src: ['test/**/*_spec.js'] },
|
||||
core: { src: ["test/_spec.js","test/unit/**/*_spec.js"]},
|
||||
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
||||
},
|
||||
webdriver: {
|
||||
all: {
|
||||
configFile: 'test/editor/wdio.conf.js'
|
||||
}
|
||||
},
|
||||
mocha_istanbul: {
|
||||
options: {
|
||||
globals: ['expect'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
ui: 'bdd',
|
||||
reportFormats: ['lcov','html'],
|
||||
print: 'both',
|
||||
istanbulOptions: ['--no-default-excludes', '-i','**/packages/node_modules/**']
|
||||
},
|
||||
all: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js","test/nodes/**/*_spec.js"] },
|
||||
core: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js"]},
|
||||
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc:true
|
||||
// http://www.jshint.com/docs/options/
|
||||
//"asi": true, // allow missing semicolons
|
||||
//"curly": true, // require braces
|
||||
//"eqnull": true, // ignore ==null
|
||||
//"forin": true, // require property filtering in "for in" loops
|
||||
//"immed": true, // require immediate functions to be wrapped in ( )
|
||||
//"nonbsp": true, // warn on unexpected whitespace breaking chars
|
||||
////"strict": true, // commented out for now as it causes 100s of warnings, but want to get there eventually
|
||||
//"loopfunc": true, // allow functions to be defined in loops
|
||||
//"sub": true // don't warn that foo['bar'] should be written as foo.bar
|
||||
},
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'red.js',
|
||||
'packages/**/*.js'
|
||||
],
|
||||
core: {
|
||||
files: {
|
||||
src: [
|
||||
'Gruntfile.js',
|
||||
'red.js',
|
||||
'packages/**/*.js',
|
||||
]
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
files: {
|
||||
src: [ 'nodes/core/*/*.js' ]
|
||||
}
|
||||
},
|
||||
editor: {
|
||||
files: {
|
||||
src: [ 'editor/js/**/*.js' ]
|
||||
}
|
||||
},
|
||||
tests: {
|
||||
files: {
|
||||
src: ['test/**/*.js']
|
||||
},
|
||||
options: {
|
||||
"expr": true
|
||||
}
|
||||
}
|
||||
},
|
||||
concat: {
|
||||
options: {
|
||||
separator: ";",
|
||||
},
|
||||
build: {
|
||||
src: [
|
||||
// Ensure editor source files are concatenated in
|
||||
// the right order
|
||||
"packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/red.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/events.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/i18n.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/settings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/nodes.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/history.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/validators.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/checkboxSet.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/panels.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/palette.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editor.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tray.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/library.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/search.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js"
|
||||
],
|
||||
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
|
||||
},
|
||||
vendor: {
|
||||
files: {
|
||||
"packages/node_modules/@node-red/editor-client/public/vendor/vendor.js": [
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-1.11.3.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/bootstrap/js/bootstrap.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js"
|
||||
],
|
||||
"packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [
|
||||
// TODO: resolve relative resource paths in
|
||||
// bootstrap/FA/jquery
|
||||
],
|
||||
"packages/node_modules/@node-red/editor-client/public/vendor/jsonata/jsonata.min.js": [
|
||||
"node_modules/jsonata/jsonata-es5.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js"
|
||||
],
|
||||
"packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [
|
||||
"node_modules/jsonata/jsonata-es5.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
build: {
|
||||
files: {
|
||||
'packages/node_modules/@node-red/editor-client/public/red/red.min.js': 'packages/node_modules/@node-red/editor-client/public/red/red.js',
|
||||
'packages/node_modules/@node-red/editor-client/public/red/main.min.js': 'packages/node_modules/@node-red/editor-client/public/red/main.js',
|
||||
'packages/node_modules/@node-red/editor-client/public/vendor/ace/mode-jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js',
|
||||
'packages/node_modules/@node-red/editor-client/public/vendor/ace/snippets/jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/snippets-jsonata.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
sass: {
|
||||
build: {
|
||||
options: {
|
||||
outputStyle: 'compressed'
|
||||
},
|
||||
files: [{
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/style.min.css',
|
||||
src: 'packages/node_modules/@node-red/editor-client/src/sass/style.scss'
|
||||
},
|
||||
{
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/bootstrap/css/bootstrap.min.css',
|
||||
src: 'packages/node_modules/@node-red/editor-client/src/vendor/bootstrap/css/bootstrap.css'
|
||||
}]
|
||||
}
|
||||
},
|
||||
jsonlint: {
|
||||
messages: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/nodes/locales/**/*.json',
|
||||
'packages/node_modules/@node-red/editor-client/locales/**/*.json',
|
||||
'packages/node_modules/@node-red/runtime/locales/**/*.json'
|
||||
]
|
||||
},
|
||||
keymaps: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/editor-client/src/js/keymap.json'
|
||||
]
|
||||
}
|
||||
},
|
||||
attachCopyright: {
|
||||
js: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/editor-client/public/red/red.min.js',
|
||||
'packages/node_modules/@node-red/editor-client/public/red/main.min.js'
|
||||
]
|
||||
},
|
||||
css: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/editor-client/public/red/style.min.css'
|
||||
]
|
||||
}
|
||||
},
|
||||
clean: {
|
||||
build: {
|
||||
src: [
|
||||
"packages/node_modules/@node-red/editor-client/public/red",
|
||||
"packages/node_modules/@node-red/editor-client/public/index.html",
|
||||
"packages/node_modules/@node-red/editor-client/public/favicon.ico",
|
||||
"packages/node_modules/@node-red/editor-client/public/icons",
|
||||
"packages/node_modules/@node-red/editor-client/public/vendor"
|
||||
]
|
||||
},
|
||||
release: {
|
||||
src: [
|
||||
'<%= paths.dist %>'
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
js: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/editor-client/src/js/**/*.js'
|
||||
],
|
||||
tasks: ['copy:build','concat','uglify','attachCopyright:js']
|
||||
},
|
||||
sass: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/editor-client/src/sass/**/*.scss'
|
||||
],
|
||||
tasks: ['sass','attachCopyright:css']
|
||||
},
|
||||
json: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/nodes/locales/**/*.json',
|
||||
'packages/node_modules/@node-red/editor-client/locales/**/*.json',
|
||||
'packages/node_modules/@node-red/runtime/locales/**/*.json'
|
||||
],
|
||||
tasks: ['jsonlint:messages']
|
||||
},
|
||||
keymaps: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/editor-client/src/js/keymap.json'
|
||||
],
|
||||
tasks: ['jsonlint:keymaps','copy:build']
|
||||
},
|
||||
misc: {
|
||||
files: [
|
||||
'CHANGELOG.md'
|
||||
],
|
||||
tasks: ['copy:build']
|
||||
}
|
||||
},
|
||||
|
||||
nodemon: {
|
||||
/* uses .nodemonignore */
|
||||
dev: {
|
||||
script: 'packages/node_modules/node-red/red.js',
|
||||
options: {
|
||||
args: nodemonArgs,
|
||||
ext: 'js,html,json',
|
||||
watch: [
|
||||
'packages/node_modules',
|
||||
'!packages/node_modules/@node-red/editor-client'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
concurrent: {
|
||||
dev: {
|
||||
tasks: ['nodemon', 'watch'],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copy: {
|
||||
build: {
|
||||
files:[
|
||||
{
|
||||
src: 'packages/node_modules/@node-red/editor-client/src/js/main.js',
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/main.js'
|
||||
},
|
||||
{
|
||||
src: 'packages/node_modules/@node-red/editor-client/src/js/keymap.json',
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/keymap.json'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/images',
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/images/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/vendor',
|
||||
src: [
|
||||
'ace/**',
|
||||
//'bootstrap/css/**',
|
||||
'bootstrap/img/**',
|
||||
'jquery/css/**',
|
||||
'font-awesome/**'
|
||||
],
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/icons',
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/icons/'
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
src: ['packages/node_modules/@node-red/editor-client/src/index.html','packages/node_modules/@node-red/editor-client/src/favicon.ico'],
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/',
|
||||
flatten: true
|
||||
},
|
||||
{
|
||||
src: 'CHANGELOG.md',
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/about'
|
||||
},
|
||||
{
|
||||
src: 'CHANGELOG.md',
|
||||
dest: 'packages/node_modules/node-red/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/ace/bin/',
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/ace/'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
chmod: {
|
||||
options: {
|
||||
mode: '755'
|
||||
},
|
||||
release: {
|
||||
src: [
|
||||
"packages/node_modules/@node-red/nodes/core/hardware/nrgpio",
|
||||
"packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-*sh"
|
||||
]
|
||||
}
|
||||
},
|
||||
'npm-command': {
|
||||
options: {
|
||||
cmd: "pack",
|
||||
cwd: "<%= paths.dist %>/modules"
|
||||
},
|
||||
'node-red': { options: { args: [__dirname+'/packages/node_modules/node-red'] } },
|
||||
'@node-red/editor-api': { options: { args: [__dirname+'/packages/node_modules/@node-red/editor-api'] } },
|
||||
'@node-red/editor-client': { options: { args: [__dirname+'/packages/node_modules/@node-red/editor-client'] } },
|
||||
'@node-red/nodes': { options: { args: [__dirname+'/packages/node_modules/@node-red/nodes'] } },
|
||||
'@node-red/registry': { options: { args: [__dirname+'/packages/node_modules/@node-red/registry'] } },
|
||||
'@node-red/runtime': { options: { args: [__dirname+'/packages/node_modules/@node-red/runtime'] } },
|
||||
'@node-red/util': { options: { args: [__dirname+'/packages/node_modules/@node-red/util'] } }
|
||||
|
||||
|
||||
},
|
||||
mkdir: {
|
||||
release: {
|
||||
options: {
|
||||
create: ['<%= paths.dist %>/modules']
|
||||
},
|
||||
},
|
||||
},
|
||||
compress: {
|
||||
release: {
|
||||
options: {
|
||||
archive: '<%= paths.dist %>/node-red-<%= pkg.version %>.zip'
|
||||
},
|
||||
expand: true,
|
||||
cwd: 'packages/node_modules/',
|
||||
src: [
|
||||
'**',
|
||||
'!@node-red/editor-client/src/**'
|
||||
]
|
||||
}
|
||||
},
|
||||
jsdoc : {
|
||||
modules: {
|
||||
src: [
|
||||
'packages/node_modules/node-red/lib/red.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/index.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/api/*.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/events.js',
|
||||
'packages/node_modules/@node-red/util/**/*.js',
|
||||
],
|
||||
options: {
|
||||
destination: 'docs',
|
||||
configure: './jsdoc.json'
|
||||
}
|
||||
},
|
||||
editor: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/editor-client/src/js'
|
||||
],
|
||||
options: {
|
||||
destination: 'packages/node_modules/@node-red/editor-client/docs',
|
||||
configure: './jsdoc.json'
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
jsdoc2md: {
|
||||
runtimeAPI: {
|
||||
options: {
|
||||
separators: true
|
||||
},
|
||||
src: [
|
||||
'packages/node_modules/@node-red/runtime/lib/index.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/api/*.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/events.js'
|
||||
],
|
||||
dest: 'packages/node_modules/@node-red/runtime/docs/api.md'
|
||||
},
|
||||
nodeREDUtil: {
|
||||
options: {
|
||||
separators: true
|
||||
},
|
||||
src: 'packages/node_modules/@node-red/util/**/*.js',
|
||||
dest: 'packages/node_modules/@node-red/util/docs/api.md'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
grunt.loadNpmTasks('grunt-simple-mocha');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
|
||||
grunt.registerTask('default', ['jshint:core','jshint:tests','jshint:editor','simplemocha:core','simplemocha:nodes']);
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-concurrent');
|
||||
grunt.loadNpmTasks('grunt-sass');
|
||||
grunt.loadNpmTasks('grunt-nodemon');
|
||||
grunt.loadNpmTasks('grunt-contrib-compress');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-chmod');
|
||||
grunt.loadNpmTasks('grunt-jsonlint');
|
||||
grunt.loadNpmTasks('grunt-mocha-istanbul');
|
||||
grunt.loadNpmTasks('grunt-webdriver');
|
||||
grunt.loadNpmTasks('grunt-jsdoc');
|
||||
grunt.loadNpmTasks('grunt-jsdoc-to-markdown');
|
||||
grunt.loadNpmTasks('grunt-npm-command');
|
||||
grunt.loadNpmTasks('grunt-mkdir');
|
||||
|
||||
grunt.registerMultiTask('attachCopyright', function() {
|
||||
var files = this.data.src;
|
||||
var copyright = "/**\n"+
|
||||
" * Copyright JS Foundation and other contributors, http://js.foundation\n"+
|
||||
" *\n"+
|
||||
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"+
|
||||
" * you may not use this file except in compliance with the License.\n"+
|
||||
" * You may obtain a copy of the License at\n"+
|
||||
" *\n"+
|
||||
" * http://www.apache.org/licenses/LICENSE-2.0\n"+
|
||||
" *\n"+
|
||||
" * Unless required by applicable law or agreed to in writing, software\n"+
|
||||
" * distributed under the License is distributed on an \"AS IS\" BASIS,\n"+
|
||||
" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"+
|
||||
" * See the License for the specific language governing permissions and\n"+
|
||||
" * limitations under the License.\n"+
|
||||
" **/\n";
|
||||
|
||||
if (files) {
|
||||
for (var i=0; i<files.length; i++) {
|
||||
var file = files[i];
|
||||
if (!grunt.file.exists(file)) {
|
||||
grunt.log.warn('File '+ file + ' not found');
|
||||
return false;
|
||||
} else {
|
||||
var content = grunt.file.read(file);
|
||||
if (content.indexOf(copyright) == -1) {
|
||||
content = copyright+content;
|
||||
if (!grunt.file.write(file, content)) {
|
||||
return false;
|
||||
}
|
||||
grunt.log.writeln("Attached copyright to "+file);
|
||||
} else {
|
||||
grunt.log.writeln("Copyright already on "+file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('verifyPackageDependencies', function() {
|
||||
var done = this.async();
|
||||
var verifyDependencies = require("./scripts/verify-package-dependencies.js");
|
||||
verifyDependencies().then(function(failures) {
|
||||
if (failures.length > 0) {
|
||||
failures.forEach(f => grunt.log.error(f));
|
||||
grunt.fail.fatal("Failed to verify package dependencies");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
grunt.registerTask('setDevEnv',
|
||||
'Sets NODE_ENV=development so non-minified assets are used',
|
||||
function () {
|
||||
process.env.NODE_ENV = 'development';
|
||||
});
|
||||
|
||||
grunt.registerTask('default',
|
||||
'Builds editor content then runs code style checks and unit tests on all components',
|
||||
['build','verifyPackageDependencies','jshint:editor','mocha_istanbul:all']);
|
||||
|
||||
grunt.registerTask('test-core',
|
||||
'Runs code style check and unit tests on core runtime code',
|
||||
['build','mocha_istanbul:core']);
|
||||
|
||||
grunt.registerTask('test-editor',
|
||||
'Runs code style check on editor code',
|
||||
['jshint:editor']);
|
||||
|
||||
grunt.registerTask('test-ui',
|
||||
'Builds editor content then runs unit tests on editor ui',
|
||||
['build','jshint:editor','webdriver:all']);
|
||||
|
||||
grunt.registerTask('test-nodes',
|
||||
'Runs unit tests on core nodes',
|
||||
['build','mocha_istanbul:nodes']);
|
||||
|
||||
grunt.registerTask('build',
|
||||
'Builds editor content',
|
||||
['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']);
|
||||
|
||||
grunt.registerTask('dev',
|
||||
'Developer mode: run node-red, watch for source changes and build/restart',
|
||||
['build','setDevEnv','concurrent:dev']);
|
||||
|
||||
grunt.registerTask('release',
|
||||
'Create distribution zip file',
|
||||
['build','verifyPackageDependencies','clean:release','mkdir:release','chmod:release','compress:release','pack-modules']);
|
||||
|
||||
grunt.registerTask('pack-modules',
|
||||
'Create module pack files for release',
|
||||
['mkdir:release','npm-command']);
|
||||
|
||||
|
||||
grunt.registerTask('coverage',
|
||||
'Run Istanbul code test coverage task',
|
||||
['build','mocha_istanbul:all']);
|
||||
|
||||
grunt.registerTask('docs',
|
||||
'Generates API documentation',
|
||||
['jsdoc','jsdoc2md']);
|
||||
};
|
||||
|
||||
57
INSTALL.md
57
INSTALL.md
@@ -1,57 +0,0 @@
|
||||
Node-RED Install
|
||||
================
|
||||
|
||||
## Install node.js
|
||||
|
||||
You can get the latest version from <http://nodejs.org/download/>.
|
||||
|
||||
Or, you may want to use a version from your operating system's package manager:
|
||||
<https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager>
|
||||
|
||||
## Get Node-RED
|
||||
|
||||
Clone the repository from GitHub:
|
||||
|
||||
$ git clone git@github.com:node-red/node-red.git
|
||||
|
||||
## Install the pre-requisite modules
|
||||
|
||||
From the top-level directory of Node-RED, run:
|
||||
|
||||
$ npm install
|
||||
|
||||
This will install the core pre-requisite modules.
|
||||
|
||||
## Run Node-RED
|
||||
|
||||
From the top-level directory, run:
|
||||
|
||||
$ node red.js
|
||||
|
||||
You can then access Node-RED at <http://localhost:1880>.
|
||||
|
||||
Online documentation is available at <http://nodered.org/docs>.
|
||||
|
||||
## Installing individual node dependencies
|
||||
|
||||
When Node-RED starts, it attempts to load the nodes from the `nodes/` directory.
|
||||
Each will have its own set of dependencies that will need to be installed before
|
||||
the node is available in the palette.
|
||||
|
||||
To help identify the dependencies, Node-RED logs any modules it fails to find
|
||||
for a particular node. You don't have to install these unless you want or need
|
||||
that node to appear.
|
||||
|
||||
Alternatively, a node's `.js` file can be examined to identify the modules it
|
||||
explicitly requires. For example, the Twitter node is defined in
|
||||
`nodes/social/27-twitter.js` and contains:
|
||||
|
||||
var RED = require("../../red/red");
|
||||
var ntwitter = require('ntwitter');
|
||||
var OAuth= require('oauth').OAuth;
|
||||
|
||||
Of these, `ntwitter` and `oauth` are neither built-in modules nor ones provided
|
||||
by Node-RED itself. They can subsequently be installed by running:
|
||||
|
||||
$ npm install ntwitter oauth
|
||||
|
||||
1
LICENSE
1
LICENSE
@@ -1,3 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
||||
56
README.md
56
README.md
@@ -2,49 +2,69 @@
|
||||
|
||||
http://nodered.org
|
||||
|
||||
[](https://travis-ci.org/node-red/node-red) [](https://coveralls.io/r/node-red/node-red?branch=master)
|
||||
|
||||
[](https://travis-ci.org/node-red/node-red)
|
||||
[](https://coveralls.io/r/node-red/node-red?branch=master)
|
||||
|
||||
A visual tool for wiring the Internet of Things.
|
||||
|
||||

|
||||

|
||||
|
||||
## Quick Start
|
||||
|
||||
Check out [INSTALL](INSTALL.md) for full instructions on getting started.
|
||||
Check out http://nodered.org/docs/getting-started/ for full instructions on getting
|
||||
started.
|
||||
|
||||
1. download the zip and unzip, or git clone
|
||||
2. cd node-red
|
||||
3. npm install
|
||||
4. node red.js
|
||||
5. Open <http://localhost:1880>
|
||||
1. `sudo npm install -g --unsafe-perm node-red`
|
||||
2. `node-red`
|
||||
3. Open <http://localhost:1880>
|
||||
|
||||
## Getting Help
|
||||
|
||||
More documentation can be found [here](http://nodered.org/docs).
|
||||
|
||||
For further help, or general discussion, please use the [mailing list](https://groups.google.com/forum/#!forum/node-red).
|
||||
For further help, or general discussion, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
||||
|
||||
## Browser Support
|
||||
## Developers
|
||||
|
||||
The Node-RED editor runs in the browser. We routinely develop and test using
|
||||
Chrome and Firefox. We have anecdotal evidence that it works in recent versions of IE.
|
||||
If you want to run the latest code from git, here's how to get started:
|
||||
|
||||
We have basic support for using in mobile/tablet browsers.
|
||||
1. Clone the code:
|
||||
|
||||
git clone https://github.com/node-red/node-red.git
|
||||
cd node-red
|
||||
|
||||
2. Install the node-red dependencies
|
||||
|
||||
npm install
|
||||
|
||||
3. Build the code
|
||||
|
||||
npm run build
|
||||
|
||||
4. Run
|
||||
|
||||
npm start
|
||||
|
||||
## Contributing
|
||||
|
||||
Before raising a pull-request, please read our [contributing guide](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md).
|
||||
Before raising a pull-request, please read our
|
||||
[contributing guide](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md).
|
||||
|
||||
This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable
|
||||
behavior to any of the project's core team at team@nodered.org.
|
||||
|
||||
## Authors
|
||||
|
||||
Node-RED is a creation of [IBM Emerging Technology](http://ibm.com/blogs/et).
|
||||
Node-RED is a project of the [JS Foundation](http://js.foundation).
|
||||
|
||||
It was created by [IBM Emerging Technology](https://www.ibm.com/blogs/emerging-technology/).
|
||||
|
||||
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
||||
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
||||
|
||||
For more open-source projects from IBM, head over [here](http://ibm.github.io).
|
||||
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright 2013, 2015 IBM Corp. under [the Apache 2.0 license](LICENSE).
|
||||
Copyright JS Foundation and other contributors, http://js.foundation under [the Apache 2.0 license](LICENSE).
|
||||
|
||||
26
jsdoc.json
Normal file
26
jsdoc.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"opts": {
|
||||
"template": "./node_modules/jsdoc-nr-template",
|
||||
"destination": "./docs",
|
||||
"recurse": true
|
||||
},
|
||||
"tags": {
|
||||
"allowUnknownTags": false,
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"_include": [
|
||||
"./packages/node_modules/@node-red/runtime/lib/api"
|
||||
]
|
||||
},
|
||||
"templates": {
|
||||
"systemName": "Node-RED Runtime API",
|
||||
"theme":"yeti",
|
||||
"footer": "",
|
||||
"copyright": "Released under the Apache License v2.0",
|
||||
"default": {
|
||||
"outputSourceFiles": false
|
||||
}
|
||||
},
|
||||
"plugins": ["plugins/markdown"]
|
||||
}
|
||||
1
lib/.gitignore
vendored
1
lib/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*
|
||||
@@ -1,49 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="sentiment">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="sentiment">
|
||||
<p>Analyses the <b>msg.payload</b> and adds a <b>msg.sentiment</b> object that contains the resulting AFINN-111 sentiment score as <b>msg.sentiment.score</b>.</p>
|
||||
<p>A score greater than zero is positive and less than zero is negative.</p>
|
||||
<p>The score typically ranges from -5 to +5, but can go higher and lower.</p>
|
||||
<p>An object of word score overrides can be supplied as <b>msg.overrides</b>.</p>
|
||||
<p>See <a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_new">the Sentiment docs here</a>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('sentiment',{
|
||||
category: 'analysis-function',
|
||||
color:"#E6E0F8",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "arrow-in.png",
|
||||
label: function() {
|
||||
return this.name||"sentiment";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,503 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="inject">
|
||||
<div class="form-row node-input-payload">
|
||||
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label>
|
||||
<select id="node-input-payloadType" style="width:73%">
|
||||
<option value="date">timestamp</option>
|
||||
<option value="string">string</option>
|
||||
<option value="none">blank</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-input-row-payload">
|
||||
<label for="node-input-payload"></label>
|
||||
<input type="text" id="node-input-payload" placeholder="payload" style="width:70%">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="topic" style="width: 70%x">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for=""><i class="fa fa-repeat"></i> Repeat</label>
|
||||
<select id="inject-time-type-select" style="width: 73%"><option value="none">none</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select>
|
||||
<input type="hidden" id="node-input-repeat" placeholder="payload">
|
||||
<input type="hidden" id="node-input-crontab" placeholder="payload">
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
|
||||
every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
|
||||
<select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/>
|
||||
<!-- on <select disabled id="inject-time-interval-days" class="inject-time-days"></select> -->
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
|
||||
at every <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="10">10</option>
|
||||
<option value="12">12</option>
|
||||
<option value="15">15</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="0">60</option>
|
||||
</select> minutes<br/>
|
||||
between <select id="inject-time-interval-time-start" class="inject-time-times"></select>
|
||||
and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
|
||||
<!-- on <select id="inject-time-interval-time-days" class="inject-time-days"></select> -->
|
||||
<div id="inject-time-interval-time-days" class="inject-time-days">
|
||||
<table style="width:100% !important"><tr><td valign="top">on </td><td valign="top">
|
||||
<label><input type='checkbox' class="cb1" value='1'/> Monday</label>
|
||||
<label><input type='checkbox' class="cb1" value='2'/> Tuesday</label>
|
||||
<label><input type='checkbox' class="cb1" value='3'/> Wednesday</label>
|
||||
<label><input type='checkbox' class="cb1" value='4'/> Thursday</label>
|
||||
<label><input type='checkbox' class="cb1" value='5'/> Friday</label>
|
||||
<label><input type='checkbox' class="cb1" value='6'/> Saturday</label>
|
||||
<label><input type='checkbox' class="cb1" value='0'/> Sunday</label>
|
||||
<!-- <div><input type='checkbox' id="cb1selectall" value='*'/> Everyday</div> -->
|
||||
</td></tr></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
|
||||
at <input id="inject-time-time" value="12:00"></input><br/>
|
||||
<!-- on <select id="inject-time-time-days" class="inject-time-days"></select> -->
|
||||
<div id="inject-time-time-days" class="inject-time-days">
|
||||
<table style="width:100% !important"><tr><td valign="top">on </td><td valign="top">
|
||||
<label><input type='checkbox' class="cb2" value='1'/> Monday</label>
|
||||
<label><input type='checkbox' class="cb2" value='2'/> Tuesday</label>
|
||||
<label><input type='checkbox' class="cb2" value='3'/> Wednesday</label>
|
||||
<label><input type='checkbox' class="cb2" value='4'/> Thursday</label>
|
||||
<label><input type='checkbox' class="cb2" value='5'/> Friday</label>
|
||||
<label><input type='checkbox' class="cb2" value='6'/> Saturday</label>
|
||||
<label><input type='checkbox' class="cb2" value='0'/> Sunday</label>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-once">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="name">
|
||||
</div>
|
||||
|
||||
<div class="form-tips"><b>Note:</b> "interval between times" and "at a specific time" will use cron.<br/>See info box for details.</div>
|
||||
</script>
|
||||
<style>
|
||||
.inject-time-row {
|
||||
padding-left: 110px;
|
||||
}
|
||||
.inject-time-row select {
|
||||
margin: 3px 0;
|
||||
}
|
||||
//.inject-time-days {
|
||||
// width: 262px;
|
||||
//}
|
||||
.inject-time-times {
|
||||
width: 90px;
|
||||
}
|
||||
.inject-time-row > .ui-spinner {
|
||||
height: 28px;
|
||||
margin: 3px 0;
|
||||
border-color: rgb(204, 204, 204);
|
||||
}
|
||||
#inject-time-time {
|
||||
margin-top: 3px;
|
||||
width: 75px;
|
||||
}
|
||||
.inject-time-count {
|
||||
width: 40px !important;
|
||||
}
|
||||
</style>
|
||||
<script type="text/x-red" data-help-name="inject">
|
||||
<p>Pressing the button on the left side of the node allows a message on a topic to be injected into the flow. This is mainly for test purposes.</p>
|
||||
<p>If no payload is specified the payload is set to the current time in millisecs since 1970. This allows subsequent functions to perform time based actions.</p>
|
||||
<p>The repeat function does what it says on the tin and continuously sends the payload every x seconds.</p>
|
||||
<p>The Fire once at start option actually waits 50mS before firing to give other nodes a chance to instantiate properly.</p>
|
||||
<p><b>Note: </b>"Interval between times" and "at a specific time" will use cron. This means that 20 minutes will be at the next hour, 20 minutes past and 40 minutes past - not in 20 minutes time.
|
||||
If you want every 20 minutes from now - use the basic "interval" option.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('inject',{
|
||||
category: 'input',
|
||||
color:"#a6bbcf",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:""},
|
||||
payload: {value:""},
|
||||
payloadType: {value:"date"},
|
||||
repeat: {value:""},
|
||||
crontab: {value:""},
|
||||
once: {value:false}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "inject.png",
|
||||
label: function() {
|
||||
if (this.payloadType === "string") {
|
||||
if ((this.topic !== "") && ((this.topic.length + this.payload.length) <= 32)) {
|
||||
return this.name||this.topic + ":" + this.payload;
|
||||
}
|
||||
else if (this.payload.length < 24) {
|
||||
return this.name||this.payload;
|
||||
}
|
||||
}
|
||||
if ((this.topic.length < 24) && (this.topic.length > 0)) {
|
||||
return this.name||this.topic;
|
||||
}
|
||||
else { return this.name||(this.payloadType==="date"?"timestamp":null)||"inject"; }
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#inject-time-type-select").change(function() {
|
||||
$("#node-input-crontab").val('');
|
||||
var id = $("#inject-time-type-select option:selected").val();
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-row-"+id).show();
|
||||
if ((id == "none") || (id == "interval")) {
|
||||
$("#node-once").show();
|
||||
}
|
||||
else {
|
||||
$("#node-once").hide();
|
||||
$("#node-input-once").prop('checked', false);
|
||||
}
|
||||
});
|
||||
|
||||
//$("#cb1selectall").click(function () {
|
||||
// $('.cb1').attr('checked', this.checked);
|
||||
//});
|
||||
//$(".cb1").click(function(){
|
||||
// if($(".cb1:checked").length == 7) {
|
||||
// $("#cb1selectall").attr("checked", "checked");
|
||||
// } else {
|
||||
// $("#cb1selectall").removeAttr("checked");
|
||||
// }
|
||||
//});
|
||||
|
||||
//var days = [
|
||||
// {v:"*",t:"every day"},
|
||||
// {v:"1-5",t:"Mondays to Fridays"},
|
||||
// {v:"0,6",t:"Saturdays and Sundays"},
|
||||
// {v:"1",t:"Mondays"},
|
||||
// {v:"2",t:"Tuesdays"},
|
||||
// {v:"3",t:"Wednesdays"},
|
||||
// {v:"4",t:"Thursdays"},
|
||||
// {v:"5",t:"Fridays"},
|
||||
// {v:"6",t:"Saturdays"},
|
||||
// {v:"0",t:"Sundays"}
|
||||
//];
|
||||
|
||||
//$(".inject-time-days").each(function() {
|
||||
// for (var d in days) {
|
||||
// $(this).append($("<option></option>").val(days[d].v).text(days[d].t));
|
||||
// }
|
||||
//});
|
||||
|
||||
$(".inject-time-times").each(function() {
|
||||
for (var i=0;i<24;i++) {
|
||||
var l = (i<10?"0":"")+i+":00";
|
||||
$(this).append($("<option></option>").val(i).text(l));
|
||||
}
|
||||
});
|
||||
|
||||
$(".inject-time-count").spinner({
|
||||
//max:60,
|
||||
min:1
|
||||
});
|
||||
|
||||
$("#inject-time-interval-units").change(function() {
|
||||
var units = $("#inject-time-interval-units option:selected").val();
|
||||
//$("#inject-time-interval-days").prop("disabled",(units == "s")?"disabled":false);
|
||||
//$(".inject-time-count").spinner("option","max",(units == "h")?24:60);
|
||||
});
|
||||
|
||||
$.widget( "ui.injecttimespinner", $.ui.spinner, {
|
||||
options: {
|
||||
// seconds
|
||||
step: 60 * 1000,
|
||||
// hours
|
||||
page: 60
|
||||
},
|
||||
_parse: function( value ) {
|
||||
if ( typeof value === "string" ) {
|
||||
// already a timestamp
|
||||
if ( Number( value ) == value ) {
|
||||
return Number( value );
|
||||
}
|
||||
var p = value.split(":");
|
||||
var offset = new Date().getTimezoneOffset();
|
||||
return (((Number(p[0])+1)*60)+Number(p[1])+offset)*60*1000;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
_format: function( value ) {
|
||||
var d = new Date(value);
|
||||
var h = d.getHours();
|
||||
var m = d.getMinutes();
|
||||
return ((h < 10)?"0":"")+h+":"+((m < 10)?"0":"")+m;
|
||||
}
|
||||
});
|
||||
|
||||
$("#inject-time-time").injecttimespinner();
|
||||
|
||||
var repeattype = "none";
|
||||
if (this.repeat != "" && this.repeat != 0) {
|
||||
repeattype = "interval";
|
||||
var r = "s";
|
||||
var c = this.repeat;
|
||||
if (this.repeat % 60 === 0) { r = "m"; c = c/60; }
|
||||
if (this.repeat % 1440 === 0) { r = "h"; c = c/60; }
|
||||
$("#inject-time-interval-count").val(c);
|
||||
$("#inject-time-interval-units").val(r);
|
||||
//$("#inject-time-interval-units option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
|
||||
$("#inject-time-interval-days").prop("disabled","disabled");
|
||||
} else if (this.crontab) {
|
||||
var cronparts = this.crontab.split(" ");
|
||||
var days = cronparts[4];
|
||||
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
|
||||
repeattype = "time";
|
||||
// Fixed time
|
||||
var time = cronparts[1]+":"+cronparts[0];
|
||||
$("#inject-time-time").val(time);
|
||||
$("#inject-time-type-select option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
|
||||
//$("#inject-time-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
|
||||
if (days == "*") { days = "1,2,3,4,5,6,0"; }
|
||||
var daya = days.split(",");
|
||||
for(var i = 0; i < daya.length; i++) {
|
||||
$("#inject-time-time-days [value=" + daya[i] + "]").attr("checked", "checked");
|
||||
}
|
||||
|
||||
}
|
||||
//else if (cronparts[0] == "0") {
|
||||
// // interval - hours
|
||||
// var hours = cronparts[1].slice(2);
|
||||
// repeattype = "interval";
|
||||
// $("#inject-time-interval-days").prop("disabled",false);
|
||||
// $("#inject-time-interval-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
|
||||
// $("#inject-time-interval-count").val(hours)
|
||||
// $("#inject-time-interval-units option").filter(function() {return $(this).val() == "h";}).attr('selected',true);
|
||||
//} else if (cronparts[1] == "*") {
|
||||
// // interval - minutes
|
||||
// var minutes = cronparts[0].slice(2);
|
||||
// repeattype = "interval";
|
||||
// $("#inject-time-interval-days").prop("disabled",false);
|
||||
// $("#inject-time-interval-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
|
||||
// $("#inject-time-interval-count").val(minutes)
|
||||
// $("#inject-time-interval-units option").filter(function() {return $(this).val() == "m";}).attr('selected',true);
|
||||
//}
|
||||
else {
|
||||
repeattype = "interval-time";
|
||||
// interval - time period
|
||||
var minutes = cronparts[0].slice(2);
|
||||
if (minutes === "") { minutes = "0"; }
|
||||
$("#inject-time-interval-time-units").val(minutes);
|
||||
//$("#inject-time-interval-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
|
||||
if (days == "*") { days = "1,2,3,4,5,6,0"; }
|
||||
var daya = days.split(",");
|
||||
for(var i = 0; i < daya.length; i++) {
|
||||
$("#inject-time-interval-time-days [value=" + daya[i] + "]").attr("checked", "checked");
|
||||
}
|
||||
|
||||
var time = cronparts[1];
|
||||
var timeparts = time.split(",");
|
||||
var start;
|
||||
var end;
|
||||
if (timeparts.length == 1) {
|
||||
// 0 or 0-10
|
||||
var hours = timeparts[0].split("-");
|
||||
if (hours.length == 1) {
|
||||
if (hours[0] === "") {
|
||||
start = "0";
|
||||
end = "0";
|
||||
}
|
||||
else {
|
||||
start = hours[0];
|
||||
end = Number(hours[0])+1;
|
||||
}
|
||||
} else {
|
||||
start = hours[0];
|
||||
end = (Number(hours[1])+1)%24;
|
||||
}
|
||||
} else {
|
||||
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
|
||||
var startparts = timeparts[0].split("-");
|
||||
start = startparts[0];
|
||||
|
||||
var endparts = timeparts[1].split("-");
|
||||
if (endparts.length == 1) {
|
||||
end = Number(endparts[0])+1;
|
||||
} else {
|
||||
end = Number(endparts[1])+1;
|
||||
}
|
||||
}
|
||||
$("#inject-time-interval-time-start option").filter(function() {return $(this).val() == start;}).attr('selected',true);
|
||||
$("#inject-time-interval-time-end option").filter(function() {return $(this).val() == end;}).attr('selected',true);
|
||||
|
||||
}
|
||||
} else {
|
||||
$("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true);
|
||||
}
|
||||
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
|
||||
$("#inject-time-row-"+repeattype).show();
|
||||
|
||||
if (this.payloadType == null) {
|
||||
if (this.payload == "") {
|
||||
this.payloadType = "date";
|
||||
} else {
|
||||
this.payloadType = "string";
|
||||
}
|
||||
}
|
||||
|
||||
$("#node-input-payloadType").change(function() {
|
||||
var id = $("#node-input-payloadType option:selected").val();
|
||||
if (id === "string") {
|
||||
$("#node-input-row-payload").show();
|
||||
} else {
|
||||
$("#node-input-row-payload").hide();
|
||||
}
|
||||
});
|
||||
$("#node-input-payloadType").val(this.payloadType);
|
||||
$("#node-input-payloadType").change();
|
||||
$("#inject-time-type-select").change();
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
var repeat = "";
|
||||
var crontab = "";
|
||||
var type = $("#inject-time-type-select option:selected").val();
|
||||
if (type == "none") {
|
||||
// nothing
|
||||
} else if (type == "interval") {
|
||||
var count = $("#inject-time-interval-count").val();
|
||||
var units = $("#inject-time-interval-units option:selected").val();
|
||||
//var days = $("#inject-time-interval-days option:selected").val();
|
||||
if (units == "s") {
|
||||
repeat = count;
|
||||
} else {
|
||||
if (units == "m") {
|
||||
//crontab = "*/"+count+" * * * "+days;
|
||||
repeat = count * 60;
|
||||
} else if (units == "h") {
|
||||
//crontab = "0 */"+count+" * * "+days;
|
||||
repeat = count * 60 * 60;
|
||||
}
|
||||
}
|
||||
} else if (type == "interval-time") {
|
||||
var count = $("#inject-time-interval-time-units").val();
|
||||
var startTime = Number($("#inject-time-interval-time-start option:selected").val());
|
||||
var endTime = Number($("#inject-time-interval-time-end option:selected").val());
|
||||
//var days = $("#inject-time-interval-time-days option:selected").val();
|
||||
var days = $('.cb1:checked').map(function(_, el) {
|
||||
return $(el).val()
|
||||
}).get();
|
||||
if (days.length == 7) { days="*"; }
|
||||
else { days = days.join(","); }
|
||||
var timerange = "";
|
||||
if (startTime == endTime) {
|
||||
//TODO: invalid
|
||||
repeat = "";
|
||||
crontab = "";
|
||||
} else if (endTime == 0) {
|
||||
timerange = startTime+"-23";
|
||||
} else if (startTime+1 < endTime) {
|
||||
timerange = startTime+"-"+(endTime-1);
|
||||
} else if (startTime+1 == endTime) {
|
||||
timerange = startTime;
|
||||
} else {
|
||||
var startpart = "";
|
||||
var endpart = "";
|
||||
if (startTime == 23) {
|
||||
startpart = "23";
|
||||
} else {
|
||||
startpart = startTime+"-23";
|
||||
}
|
||||
if (endTime == 1) {
|
||||
endpart = "0";
|
||||
} else {
|
||||
endpart = "0-"+(endTime-1);
|
||||
}
|
||||
timerange = startpart+","+endpart;
|
||||
}
|
||||
repeat = "";
|
||||
if (count === "0") {
|
||||
crontab = count+" "+timerange+" * * "+days;
|
||||
}
|
||||
else {
|
||||
crontab = "*/"+count+" "+timerange+" * * "+days;
|
||||
}
|
||||
} else if (type == "time") {
|
||||
var time = $("#inject-time-time").val();
|
||||
//var days = $("#inject-time-time-days option:selected").val();
|
||||
var days = $('.cb2:checked').map(function(_, el) {
|
||||
return $(el).val()
|
||||
}).get();
|
||||
if (days.length == 7) { days="*"; }
|
||||
else { days = days.join(","); }
|
||||
var parts = time.split(":");
|
||||
repeat = "";
|
||||
crontab = parts[1]+" "+parts[0]+" * * "+days;
|
||||
}
|
||||
|
||||
$("#node-input-repeat").val(repeat);
|
||||
$("#node-input-crontab").val(crontab);
|
||||
|
||||
},
|
||||
button: {
|
||||
onclick: function() {
|
||||
var label = (this.name||this.payload).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
if (this.payloadType === "date") { label = "timestamp"; }
|
||||
if (this.payloadType === "none") { label = "blank"; }
|
||||
|
||||
$.ajax({
|
||||
url: "inject/"+this.id,
|
||||
type:"POST",
|
||||
success: function(resp) {
|
||||
RED.notify("Successfully injected: "+label,"success");
|
||||
},
|
||||
error: function(jqXHR,textStatus,errorThrown) {
|
||||
if (jqXHR.status == 404) {
|
||||
RED.notify("<strong>Error</strong>: inject node not deployed","error");
|
||||
} else if (jqXHR.status == 500) {
|
||||
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
|
||||
} else if (jqXHR.status == 0) {
|
||||
RED.notify("<strong>Error</strong>: no response from server","error");
|
||||
} else {
|
||||
RED.notify("<strong>Error</strong>: unexpected error: ("+jqXHR.status+") "+textStatus,"error");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var cron = require("cron");
|
||||
|
||||
function InjectNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.topic = n.topic;
|
||||
this.payload = n.payload;
|
||||
this.payloadType = n.payloadType;
|
||||
this.repeat = n.repeat;
|
||||
this.crontab = n.crontab;
|
||||
this.once = n.once;
|
||||
var node = this;
|
||||
this.interval_id = null;
|
||||
this.cronjob = null;
|
||||
|
||||
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
|
||||
this.repeat = this.repeat * 1000;
|
||||
this.log("repeat = "+this.repeat);
|
||||
this.interval_id = setInterval( function() {
|
||||
node.emit("input",{});
|
||||
}, this.repeat );
|
||||
} else if (this.crontab) {
|
||||
if (cron) {
|
||||
this.log("crontab = "+this.crontab);
|
||||
this.cronjob = new cron.CronJob(this.crontab,
|
||||
function() {
|
||||
node.emit("input",{});
|
||||
},
|
||||
null,true);
|
||||
} else {
|
||||
this.error("'cron' module not found");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.once) {
|
||||
setTimeout( function(){ node.emit("input",{}); }, 100);
|
||||
}
|
||||
|
||||
this.on("input",function(msg) {
|
||||
var msg = {topic:this.topic};
|
||||
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
|
||||
msg.payload = Date.now();
|
||||
} else if (this.payloadType == null || this.payloadType === "string") {
|
||||
msg.payload = this.payload;
|
||||
} else {
|
||||
msg.payload = "";
|
||||
}
|
||||
this.send(msg);
|
||||
msg = null;
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("inject",InjectNode);
|
||||
|
||||
InjectNode.prototype.close = function() {
|
||||
if (this.interval_id != null) {
|
||||
clearInterval(this.interval_id);
|
||||
this.log("inject: repeat stopped");
|
||||
} else if (this.cronjob != null) {
|
||||
this.cronjob.stop();
|
||||
this.log("inject: cronjob stopped");
|
||||
delete this.cronjob;
|
||||
}
|
||||
}
|
||||
|
||||
RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
|
||||
var node = RED.nodes.getNode(req.params.id);
|
||||
if (node != null) {
|
||||
try {
|
||||
node.receive();
|
||||
res.send(200);
|
||||
} catch(err) {
|
||||
res.send(500);
|
||||
node.error("Inject failed:"+err);
|
||||
console.log(err.stack);
|
||||
}
|
||||
} else {
|
||||
res.send(404);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="debug">
|
||||
<div class="form-row">
|
||||
<label for="node-input-select-complete"><i class="fa fa-list"></i> Output</label>
|
||||
<select type="text" id="node-input-select-complete" style="display: inline-block; width: 250px; vertical-align: top;">
|
||||
<option value="false">message property</option>
|
||||
<option value="true">complete msg object</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-prop-row">
|
||||
<label for="node-input-complete"> </label>msg.<input type="text" style="width:208px" id="node-input-complete">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-console"><i class="fa fa-random"></i> to</label>
|
||||
<select type="text" id="node-input-console" style="display: inline-block; width: 250px; vertical-align: top;">
|
||||
<option value="false">debug tab</option>
|
||||
<option value="true">debug tab and console</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="debug">
|
||||
<p>The Debug node can be connected to the output of any node. It can be used to display the output of any message property in the debug tab of the sidebar. The default is to display <b>msg.payload</b>.</p>
|
||||
<p>Each message will also display the timestamp, <b>msg.topic</b> and the property chosen to output.</p>
|
||||
<p>The sidebar can be accessed under the options drop-down in the top right corner.</p>
|
||||
<p>The button to the right of the node will toggle it's output on and off so you can de-clutter the debug window.</p>
|
||||
<p>If the payload is an object or buffer it will be stringified first for display and indicate that by saying "(Object)" or "(Buffer)".</p>
|
||||
<p>Selecting any particular message will highlight (in red) the debug node that reported it. This is useful if you wire up multiple debug nodes.</p>
|
||||
<p>Optionally can show the complete <b>msg</b> object.</p>
|
||||
<p>In addition any calls to node.warn or node.error will appear here.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function oneditprepare() {
|
||||
if (this.complete === "true" || this.complete === true) {
|
||||
// show complete message object
|
||||
$("#node-input-select-complete").val("true");
|
||||
$("#node-prop-row").hide();
|
||||
} else {
|
||||
// show msg.[ ]
|
||||
var property = (!this.complete||(this.complete === "false")) ? "payload" : this.complete+"";
|
||||
$("#node-input-select-complete").val("false");
|
||||
$("#node-input-complete").val(property);
|
||||
$("#node-prop-row").show();
|
||||
}
|
||||
$("#node-input-select-complete").change(function() {
|
||||
var v = $("#node-input-select-complete option:selected").val();
|
||||
$("#node-input-complete").val(v);
|
||||
if (v !== "true") {
|
||||
$("#node-input-complete").val("payload");
|
||||
$("#node-prop-row").show();
|
||||
$("#node-input-complete").focus();
|
||||
} else {
|
||||
$("#node-prop-row").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('debug',{
|
||||
category: 'output',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
active: {value:true},
|
||||
console: {value:"false"},
|
||||
complete: {value:"false", required:true}
|
||||
},
|
||||
label: function() {
|
||||
if (this.complete === true || this.complete === "true") {
|
||||
return this.name||"msg";
|
||||
} else {
|
||||
return this.name || "msg." + ((!this.complete || this.complete === "false") ? "payload" : this.complete);
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
color:"#87a980",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "debug.png",
|
||||
align: "right",
|
||||
button: {
|
||||
toggle: "active",
|
||||
onclick: function() {
|
||||
var label = this.name||"debug";
|
||||
$.ajax({
|
||||
url: "debug/"+this.id+"/"+(this.active?"enable":"disable"),
|
||||
type: "POST",
|
||||
success: function(resp, textStatus, xhr) {
|
||||
if (xhr.status == 200) {
|
||||
RED.notify("Successfully activated: "+label,"success");
|
||||
} else if (xhr.status == 201) {
|
||||
RED.notify("Successfully deactivated: "+label,"success");
|
||||
}
|
||||
},
|
||||
error: function(jqXHR,textStatus,errorThrown) {
|
||||
if (jqXHR.status == 404) {
|
||||
RED.notify("<strong>Error</strong>: debug node not deployed","error");
|
||||
} else if (jqXHR.status == 0) {
|
||||
RED.notify("<strong>Error</strong>: no response from server","error");
|
||||
} else {
|
||||
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+") "+err.response,"error");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onpaletteadd: function() {
|
||||
var content = document.createElement("div");
|
||||
content.id = "tab-debug";
|
||||
|
||||
var toolbar = document.createElement("div");
|
||||
toolbar.id = "debug-toolbar";
|
||||
content.appendChild(toolbar);
|
||||
|
||||
toolbar.innerHTML = '<div class="btn-group pull-right"><a id="debug-tab-clear" title="clear log" class="btn btn-mini" href="#"><i class="fa fa-trash"></i></a></div> ';
|
||||
|
||||
var messages = document.createElement("div");
|
||||
messages.id = "debug-content";
|
||||
content.appendChild(messages);
|
||||
|
||||
RED.sidebar.addTab("debug",content);
|
||||
|
||||
function getTimestamp() {
|
||||
var d = new Date();
|
||||
return d.toLocaleString();
|
||||
}
|
||||
|
||||
var sbc = document.getElementById("debug-content");
|
||||
|
||||
var messageCount = 0;
|
||||
var that = this;
|
||||
RED._debug = function(msg) {
|
||||
that.handleDebugMessage("",{
|
||||
name:"debug",
|
||||
msg:msg
|
||||
});
|
||||
}
|
||||
|
||||
this.handleDebugMessage = function(t,o) {
|
||||
var msg = document.createElement("div");
|
||||
msg.onmouseover = function() {
|
||||
msg.style.borderRightColor = "#999";
|
||||
var n = RED.nodes.node(o.id);
|
||||
if (n) {
|
||||
n.highlighted = true;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.view.redraw();
|
||||
};
|
||||
msg.onmouseout = function() {
|
||||
msg.style.borderRightColor = "";
|
||||
var n = RED.nodes.node(o.id);
|
||||
if (n) {
|
||||
n.highlighted = false;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.view.redraw();
|
||||
};
|
||||
msg.onclick = function() {
|
||||
var node = RED.nodes.node(o.id);
|
||||
if (node) {
|
||||
RED.view.showWorkspace(node.z);
|
||||
}
|
||||
|
||||
};
|
||||
var name = (o.name?o.name:o.id).toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var topic = (o.topic||"").toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var property = (o.property?o.property:'').replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var payload = (o.msg||"").toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'');
|
||||
msg.innerHTML = '<span class="debug-message-date">'+
|
||||
getTimestamp()+'</span>'+
|
||||
'<span class="debug-message-name">['+name+']</span>'+
|
||||
'<span class="debug-message-topic">'+
|
||||
(o.topic?topic+' : ':'')+
|
||||
(o.property?'[msg.'+property+']':'[msg]')+
|
||||
'</span>'+'<span class="debug-message-payload">'+
|
||||
payload+'</span>';
|
||||
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
|
||||
messageCount++;
|
||||
$(messages).append(msg);
|
||||
|
||||
if (messageCount > 200) {
|
||||
$("#debug-content .debug-message:first").remove();
|
||||
messageCount--;
|
||||
}
|
||||
if (atBottom) {
|
||||
$(sbc).scrollTop(sbc.scrollHeight);
|
||||
}
|
||||
};
|
||||
RED.comms.subscribe("debug",this.handleDebugMessage);
|
||||
|
||||
$("#debug-tab-clear").click(function() {
|
||||
$(".debug-message").remove();
|
||||
messageCount = 0;
|
||||
RED.nodes.eachNode(function(node) {
|
||||
node.highlighted = false;
|
||||
node.dirty = true;
|
||||
});
|
||||
RED.view.redraw();
|
||||
});
|
||||
},
|
||||
onpaletteremove: function() {
|
||||
RED.comms.unsubscribe("debug",this.handleDebugMessage);
|
||||
RED.sidebar.removeTab("debug");
|
||||
delete RED._debug;
|
||||
},
|
||||
oneditprepare: oneditprepare
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#debug-content {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
bottom: 0px;
|
||||
left:0px;
|
||||
right: 0px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#debug-toolbar {
|
||||
padding: 3px 10px;
|
||||
height: 24px;
|
||||
background: #f3f3f3;
|
||||
}
|
||||
.debug-message {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-left: 8px solid #eee;
|
||||
border-right: 8px solid #eee;
|
||||
padding: 2px;
|
||||
}
|
||||
.debug-message-date {
|
||||
background: #fff;
|
||||
font-size: 9px;
|
||||
color: #aaa;
|
||||
padding: 1px 5px 1px 1px;
|
||||
}
|
||||
.debug-message-topic {
|
||||
display: block;
|
||||
background: #fff;
|
||||
padding: 1px 5px;
|
||||
font-size: 9px;
|
||||
color: #a66;
|
||||
}
|
||||
.debug-message-name {
|
||||
background: #fff;
|
||||
padding: 1px 5px;
|
||||
font-size: 9px;
|
||||
color: #aac;
|
||||
}
|
||||
.debug-message-payload {
|
||||
display: block;
|
||||
padding: 2px;
|
||||
background: #fff;
|
||||
}
|
||||
.debug-message-level-log {
|
||||
border-left-color: #eee;
|
||||
border-right-color: #eee;
|
||||
}
|
||||
.debug-message-level-warn {
|
||||
border-left-color: #ffdf9d;
|
||||
border-right-color: #ffdf9d;
|
||||
}
|
||||
.debug-message-level-error {
|
||||
border-left-color: #f99;
|
||||
border-right-color: #f99;
|
||||
}
|
||||
</style>
|
||||
@@ -1,140 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var events = require("events");
|
||||
var debuglength = RED.settings.debugMaxLength||1000;
|
||||
var useColors = false;
|
||||
// util.inspect.styles.boolean = "red";
|
||||
|
||||
function DebugNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.name = n.name;
|
||||
this.complete = n.complete||"payload";
|
||||
|
||||
if (this.complete === "false") {
|
||||
this.complete = "payload";
|
||||
}
|
||||
if (this.complete === true) {
|
||||
this.complete = "true";
|
||||
}
|
||||
|
||||
this.console = n.console;
|
||||
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
|
||||
var node = this;
|
||||
|
||||
this.on("input",function(msg) {
|
||||
if (this.complete === "true") {
|
||||
// debug complete msg object
|
||||
if (this.console === "true") {
|
||||
node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
|
||||
}
|
||||
if (this.active) {
|
||||
sendDebug({id:this.id,name:this.name,topic:msg.topic,msg:msg,_path:msg._path});
|
||||
}
|
||||
} else {
|
||||
// debug user defined msg property
|
||||
var property = "payload";
|
||||
var output = msg[property];
|
||||
if (this.complete !== "false" && typeof this.complete !== "undefined") {
|
||||
property = this.complete;
|
||||
var propertyParts = property.split(".");
|
||||
try {
|
||||
output = propertyParts.reduce(function (obj, i) {
|
||||
return obj[i];
|
||||
}, msg);
|
||||
} catch (err) {
|
||||
output = undefined;
|
||||
}
|
||||
}
|
||||
if (this.console === "true") {
|
||||
if (typeof output === "string") {
|
||||
node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output);
|
||||
} else if (typeof output === "object") {
|
||||
node.log("\n"+util.inspect(output, {colors:useColors, depth:10}));
|
||||
} else {
|
||||
node.log(util.inspect(output, {colors:useColors}));
|
||||
}
|
||||
}
|
||||
if (this.active) {
|
||||
sendDebug({id:this.id,name:this.name,topic:msg.topic,property:property,msg:output,_path:msg._path});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("debug",DebugNode);
|
||||
|
||||
function sendDebug(msg) {
|
||||
if (msg.msg instanceof Error) {
|
||||
msg.msg = msg.msg.toString();
|
||||
} else if (msg.msg instanceof Buffer) {
|
||||
msg.msg = "(Buffer) "+msg.msg.toString('hex');
|
||||
} else if (typeof msg.msg === 'object') {
|
||||
var seen = [];
|
||||
var ty = "(Object) ";
|
||||
if (util.isArray(msg.msg)) { ty = "(Array) "; }
|
||||
msg.msg = ty + JSON.stringify(msg.msg, function(key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.indexOf(value) !== -1) { return "[circular]"; }
|
||||
seen.push(value);
|
||||
}
|
||||
return value;
|
||||
}," ");
|
||||
seen = null;
|
||||
} else if (typeof msg.msg === "boolean") {
|
||||
msg.msg = "(boolean) "+msg.msg.toString();
|
||||
} else if (msg.msg === 0) {
|
||||
msg.msg = "0";
|
||||
} else if (msg.msg === null || typeof msg.msg === "undefined") {
|
||||
msg.msg = "(undefined)";
|
||||
}
|
||||
|
||||
if (msg.msg.length > debuglength) {
|
||||
msg.msg = msg.msg.substr(0,debuglength) +" ....";
|
||||
}
|
||||
|
||||
RED.comms.publish("debug",msg);
|
||||
}
|
||||
|
||||
DebugNode.logHandler = new events.EventEmitter();
|
||||
DebugNode.logHandler.on("log",function(msg) {
|
||||
if (msg.level === RED.log.WARN || msg.level === RED.log.ERROR) {
|
||||
sendDebug(msg);
|
||||
}
|
||||
});
|
||||
RED.log.addHandler(DebugNode.logHandler);
|
||||
|
||||
RED.httpAdmin.post("/debug/:id/:state", RED.auth.needsPermission("debug.write"), function(req,res) {
|
||||
var node = RED.nodes.getNode(req.params.id);
|
||||
var state = req.params.state;
|
||||
if (node !== null && typeof node !== "undefined" ) {
|
||||
if (state === "enable") {
|
||||
node.active = true;
|
||||
res.send(200);
|
||||
} else if (state === "disable") {
|
||||
node.active = false;
|
||||
res.send(201);
|
||||
} else {
|
||||
res.send(404);
|
||||
}
|
||||
} else {
|
||||
res.send(404);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="exec">
|
||||
<div class="form-row">
|
||||
<label for="node-input-command"><i class="fa fa-file"></i> Command</label>
|
||||
<input type="text" id="node-input-command" placeholder="command">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-append"><i class="fa fa-list"></i> Append</label>
|
||||
<input type="text" id="node-input-append" placeholder="extra input">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-useSpawn" placeholder="spawn" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-useSpawn" style="width: 70%;">Use spawn() instead of exec() ?</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="exec">
|
||||
<p>Calls out to a system command.<br/></p>
|
||||
<p>Provides 3 outputs... stdout, stderr, and return code.</p>
|
||||
<p>By default uses exec() which calls the command, blocks while waiting for completion, and then returns the complete result in one go, along with any errors.</p>
|
||||
<p>Optionally can use spawn() instead, which returns output from stdout and stderr as the command runs (ie one line at a time). On completion it then returns a return code (on the 3rd output).</p>
|
||||
<p>Spawn only expect one command word, with all extra parameters to be comma separated and passed as the append.</p>
|
||||
<p>The optional append gets added to the command after the <b>msg.payload</b> - so you can do things like pipe the result to another command.</p>
|
||||
<p>If stdout is binary a <i>buffer</i> is returned - otherwise returns a <i>string</i>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('exec',{
|
||||
category: 'advanced-function',
|
||||
color:"darksalmon",
|
||||
defaults: {
|
||||
command: {value:"",required:true},
|
||||
append: {value:""},
|
||||
useSpawn: {value:""},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:3,
|
||||
icon: "arrow-in.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||this.command;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var spawn = require('child_process').spawn;
|
||||
var exec = require('child_process').exec;
|
||||
var isUtf8 = require('is-utf8');
|
||||
|
||||
function ExecNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.cmd = n.command.trim();
|
||||
this.append = n.append.trim() || "";
|
||||
this.useSpawn = n.useSpawn;
|
||||
|
||||
var node = this;
|
||||
this.on("input", function(msg) {
|
||||
node.status({fill:"blue",shape:"dot"});
|
||||
if (this.useSpawn === true) {
|
||||
// make the extra args into an array
|
||||
// then prepend with the msg.payload
|
||||
if (typeof(msg.payload !== "string")) { msg.payload = msg.payload.toString(); }
|
||||
var arg = [];
|
||||
if (node.append.length > 0) { arg = node.append.split(","); }
|
||||
if (msg.payload.trim() !== "") { arg.unshift(msg.payload); }
|
||||
if (RED.settings.verbose) { node.log(node.cmd+" ["+arg+"]"); }
|
||||
if (node.cmd.indexOf(" ") == -1) {
|
||||
var ex = spawn(node.cmd,arg);
|
||||
ex.stdout.on('data', function (data) {
|
||||
//console.log('[exec] stdout: ' + data);
|
||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||
else { msg.payload = data; }
|
||||
node.send([msg,null,null]);
|
||||
});
|
||||
ex.stderr.on('data', function (data) {
|
||||
//console.log('[exec] stderr: ' + data);
|
||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||
else { msg.payload = new Buffer(data); }
|
||||
node.send([null,msg,null]);
|
||||
});
|
||||
ex.on('close', function (code) {
|
||||
//console.log('[exec] result: ' + code);
|
||||
msg.payload = code;
|
||||
node.status({});
|
||||
node.send([null,null,msg]);
|
||||
});
|
||||
ex.on('error', function (code) {
|
||||
node.warn(code);
|
||||
});
|
||||
}
|
||||
else { node.error("Spawn command must be just the command - no spaces or extra parameters"); }
|
||||
}
|
||||
else {
|
||||
var cl = node.cmd+" "+msg.payload+" "+node.append;
|
||||
if (RED.settings.verbose) { node.log(cl); }
|
||||
var child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
|
||||
msg.payload = new Buffer(stdout,"binary");
|
||||
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
|
||||
var msg2 = {payload:stderr};
|
||||
var msg3 = null;
|
||||
//console.log('[exec] stdout: ' + stdout);
|
||||
//console.log('[exec] stderr: ' + stderr);
|
||||
if (error !== null) {
|
||||
msg3 = {payload:error};
|
||||
//console.log('[exec] error: ' + error);
|
||||
}
|
||||
node.status({});
|
||||
node.send([msg,msg2,msg3]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("exec",ExecNode);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013, 2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="function">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom: 0px;">
|
||||
<label for="node-input-func"><i class="fa fa-wrench"></i> Function</label>
|
||||
<input type="hidden" id="node-input-func" autofocus="autofocus">
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-outputs"><i class="fa fa-random"></i> Outputs</label>
|
||||
<input id="node-input-outputs" style="width: 60px; height: 1.7em;" value="1">
|
||||
</div>
|
||||
<div class="form-tips">See the Info tab for help writing functions.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="function">
|
||||
<p>A function block where you can write code to do more interesting things.</p>
|
||||
<p>The message is passed in as a JavaScript object called <code>msg</code>.</p>
|
||||
<p>By convention it will have a <code>msg.payload</code> property containing
|
||||
the body of the message.</p>
|
||||
<p>The function should return the messages it wants to pass on to the next nodes
|
||||
in the flow. It can return:</p>
|
||||
<ul>
|
||||
<li>a single message object - passed to nodes connected to the first output</li>
|
||||
<li>an array of message objects - passed to nodes connected to the corresponding outputs</li>
|
||||
</ul>
|
||||
<p>If any element of the array is itself an array of messages, multiple
|
||||
messages are sent to the corresponding output.</p>
|
||||
<p>If null is returned, either by itself or as an element of the array, no
|
||||
message is passed on.</p>
|
||||
<p>See the <a target="_new" href="http://nodered.org/docs/writing-functions.html">online documentation</a> for more help.</p>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('function',{
|
||||
color:"#fdd0a2",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
func: {value:"\nreturn msg;"},
|
||||
outputs: {value:1}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "function.png",
|
||||
label: function() {
|
||||
return this.name;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$( "#node-input-outputs" ).spinner({
|
||||
min:1
|
||||
});
|
||||
|
||||
function functionDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
};
|
||||
|
||||
$( "#dialog" ).on("dialogresize", functionDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-function');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
functionDialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",functionDialogResize);
|
||||
});
|
||||
var that = this;
|
||||
require(["orion/editor/edit"], function(edit) {
|
||||
that.editor = edit({
|
||||
parent:document.getElementById('node-input-func-editor'),
|
||||
lang:"js",
|
||||
contents: $("#node-input-func").val()
|
||||
});
|
||||
RED.library.create({
|
||||
url:"functions", // where to get the data from
|
||||
type:"function", // the type of object the library is for
|
||||
editor:that.editor, // the field name the main text body goes to
|
||||
fields:['name','outputs']
|
||||
});
|
||||
$("#node-input-name").focus();
|
||||
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
$("#node-input-func").val(this.editor.getText())
|
||||
delete this.editor;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var vm = require("vm");
|
||||
|
||||
function FunctionNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.name = n.name;
|
||||
this.func = n.func;
|
||||
var functionText = "var results = null; results = (function(msg){\n"+this.func+"\n})(msg);";
|
||||
this.topic = n.topic;
|
||||
var sandbox = {
|
||||
console:console,
|
||||
util:util,
|
||||
Buffer:Buffer,
|
||||
context: {
|
||||
global:RED.settings.functionGlobalContext || {}
|
||||
}
|
||||
};
|
||||
var context = vm.createContext(sandbox);
|
||||
try {
|
||||
this.script = vm.createScript(functionText);
|
||||
this.on("input", function(msg) {
|
||||
try {
|
||||
var start = process.hrtime();
|
||||
context.msg = msg;
|
||||
this.script.runInContext(context);
|
||||
var results = context.results;
|
||||
if (results == null) {
|
||||
results = [];
|
||||
} else if (results.length == null) {
|
||||
results = [results];
|
||||
}
|
||||
if (msg._topic) {
|
||||
for (var m in results) {
|
||||
if (results[m]) {
|
||||
if (util.isArray(results[m])) {
|
||||
for (var n=0; n < results[m].length; n++) {
|
||||
results[m][n]._topic = msg._topic;
|
||||
}
|
||||
} else {
|
||||
results[m]._topic = msg._topic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.send(results);
|
||||
var duration = process.hrtime(start);
|
||||
var converted = Math.floor((duration[0]* 1e9 + duration[1])/10000)/100;
|
||||
this.metric("duration", msg, converted);
|
||||
if (process.env.NODE_RED_FUNCTION_TIME) {
|
||||
this.status({fill:"yellow",shape:"dot",text:""+converted});
|
||||
}
|
||||
} catch(err) {
|
||||
var errorMessage = err.toString();
|
||||
var stack = err.stack.split(/\r?\n/);
|
||||
if (stack.length > 0) {
|
||||
var m = /at undefined:(\d+):(\d+)$/.exec(stack[1]);
|
||||
if (m) {
|
||||
var line = Number(m[1])-1;
|
||||
var cha = m[2];
|
||||
errorMessage += " (line "+line+", col "+cha+")";
|
||||
}
|
||||
}
|
||||
this.error(errorMessage);
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
// eg SyntaxError - which v8 doesn't include line number information
|
||||
// so we can't do better than this
|
||||
this.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType("function",FunctionNode);
|
||||
RED.library.register("functions");
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="template">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom: 0px;">
|
||||
<label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</label>
|
||||
<input type="hidden" id="node-input-template" autofocus="autofocus">
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-field"><i class="fa fa-edit"></i> Property</label>
|
||||
msg.<input type="text" id="node-input-field" placeholder="payload" style="width: 64%;">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="template">
|
||||
<p>Creates a new message based on the provided template.</p>
|
||||
<p>This uses the <i><a href="http://mustache.github.io/mustache.5.html" target="_new">mustache</a></i> format.</p>
|
||||
<p>For example, when a template of:
|
||||
<pre>Hello {{name}}. Today is {{date}}</pre>
|
||||
<p>receives a message containing:
|
||||
<pre>{
|
||||
name: "Fred",
|
||||
date: "Monday"
|
||||
payload: ...
|
||||
}</pre>
|
||||
<p>The resulting payload will be:
|
||||
<pre>Hello Fred. Today is Monday</pre>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('template',{
|
||||
color:"rgb(243, 181, 103)",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
field: {value:"payload"},
|
||||
template: {value:"This is the payload: {{payload}}!"},
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "template.png",
|
||||
label: function() {
|
||||
return this.name;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
|
||||
function templateDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
};
|
||||
|
||||
$( "#dialog" ).on("dialogresize", templateDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-template');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
templateDialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",templateDialogResize);
|
||||
});
|
||||
|
||||
var that = this;
|
||||
require(["orion/editor/edit"], function(edit) {
|
||||
that.editor = edit({
|
||||
parent:document.getElementById('node-input-template-editor'),
|
||||
lang:"html",
|
||||
contents: $("#node-input-template").val()
|
||||
});
|
||||
RED.library.create({
|
||||
url:"templates", // where to get the data from
|
||||
type:"template", // the type of object the library is for
|
||||
editor:that.editor, // the field name the main text body goes to
|
||||
fields:['name','field']
|
||||
});
|
||||
$("#node-input-name").focus();
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
$("#node-input-template").val(this.editor.getText())
|
||||
delete this.editor;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var mustache = require("mustache");
|
||||
|
||||
function TemplateNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.name = n.name;
|
||||
this.field = n.field || "payload";
|
||||
this.template = n.template;
|
||||
var node = this;
|
||||
|
||||
var b = node.field.split(".");
|
||||
var i = 0;
|
||||
var m = null;
|
||||
var rec = function(obj) {
|
||||
i += 1;
|
||||
if ((i < b.length) && (typeof obj[b[i-1]] === "object")) {
|
||||
rec(obj[b[i-1]]); // not there yet - carry on digging
|
||||
}
|
||||
else {
|
||||
if (i === b.length) { // we've finished so assign the value
|
||||
obj[b[i-1]] = mustache.render(node.template,m);
|
||||
node.send(m);
|
||||
}
|
||||
else {
|
||||
obj[b[i-1]] = {}; // needs to be a new object so create it
|
||||
rec(obj[b[i-1]]); // and carry on digging
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.on("input", function(msg) {
|
||||
try {
|
||||
m = msg;
|
||||
i = 0;
|
||||
rec(msg);
|
||||
} catch(err) {
|
||||
node.error(err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("template",TemplateNode);
|
||||
RED.library.register("templates");
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- First, the content of the edit dialog is defined. -->
|
||||
<script type="text/x-red" data-template-name="delay">
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> Action</label>
|
||||
<select id="node-input-pauseType" style="width:270px !important">
|
||||
<option value="delay">Delay message</option>
|
||||
<option value="random">Random delay</option>
|
||||
<option value="rate">Limit rate to</option>
|
||||
<option value="queue">Topic based fair queue</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="delay-details" class="form-row">
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> For</label>
|
||||
<input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important">
|
||||
<select id="node-input-timeoutUnits" style="width:200px !important">
|
||||
<option value="milliseconds">Milliseconds</option>
|
||||
<option value="seconds">Seconds</option>
|
||||
<option value="minutes">Minutes</option>
|
||||
<option value="hours">Hours</option>
|
||||
<option value="days">Days</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="rate-details" class="form-row">
|
||||
<label for="node-input-rate"><i class="fa fa-clock-o"></i> Rate</label>
|
||||
<input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important">
|
||||
<label for="node-input-rateUnits">msg(s) per</label>
|
||||
<select id="node-input-rateUnits" style="width:140px !important">
|
||||
<option value="second">Second</option>
|
||||
<option value="minute">Minute</option>
|
||||
<option value="hour">Hour</option>
|
||||
<option value="day">Day</option>
|
||||
</select>
|
||||
<br/>
|
||||
<div id="node-input-dr"><input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop">drop intermediate messages</label></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="random-details" class="form-row">
|
||||
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> Between</label>
|
||||
<input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important">
|
||||
<label for="node-input-randomLast" style="width:20px"> & </label>
|
||||
<input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important">
|
||||
<select id="node-input-randomUnits" style="width:140px !important">
|
||||
<option value="milliseconds">Milliseconds</option>
|
||||
<option value="seconds">Seconds</option>
|
||||
<option value="minutes">Minutes</option>
|
||||
<option value="hours">Hours</option>
|
||||
<option value="days">Days</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Next, some simple help text is provided for the node. -->
|
||||
<script type="text/x-red" data-help-name="delay">
|
||||
<p>Introduces a delay into a flow or rate limits messages.</p>
|
||||
<p>Default delay is 5 seconds and rate limit of 1 msg/second, but both can be configured.</p>
|
||||
<p>If you select a rate limit you may optionally discard any intermediate messages that arrive.</p>
|
||||
<p>The "topic based fair queue" adds messages to a release queue tagged by their <b>msg.topic</b> property.
|
||||
At each "tick", derived from the rate, the next "topic" is released.
|
||||
Any messages arriving on the same topic before release replace those in that position in the queue.
|
||||
So each "topic" gets a turn - but the most recent value is always the one sent.</p>
|
||||
</script>
|
||||
|
||||
<!-- Finally, the node type is registered along with all of its properties -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('delay',{
|
||||
category: 'function', // the palette category
|
||||
color:"#E6E0F8",
|
||||
defaults: { // defines the editable properties of the node
|
||||
name: {value:""}, // along with default values.
|
||||
pauseType: {value:"delay", required:true},
|
||||
timeout: {value:"5", required:true, validate:RED.validators.number()},
|
||||
timeoutUnits: {value:"seconds"},
|
||||
rate: {value:"1", required:true, validate:RED.validators.number()},
|
||||
rateUnits: {value: "second"},
|
||||
randomFirst: {value:"1", required:true, validate:RED.validators.number()},
|
||||
randomLast: {value:"5", required:true, validate:RED.validators.number()},
|
||||
randomUnits: {value: "seconds"},
|
||||
drop: {value:false}
|
||||
},
|
||||
inputs:1, // set the number of inputs - only 0 or 1
|
||||
outputs:1, // set the number of outputs - 0 to n
|
||||
icon: "timer.png", // set the icon (held in public/icons)
|
||||
label: function() { // sets the default label contents
|
||||
if (this.pauseType == "delay") {
|
||||
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
|
||||
if (this.timeoutUnits == "milliseconds") { units = "ms"; }
|
||||
return this.name||"delay "+this.timeout+" " + units;
|
||||
} else if (this.pauseType == "rate") {
|
||||
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
|
||||
return this.name||"limit "+this.rate+" msg/"+ units;
|
||||
} else if (this.pauseType == "random") {
|
||||
return this.name || "random";
|
||||
}
|
||||
else {
|
||||
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
|
||||
return this.name || "queue" +this.rate+" msg/"+ units;
|
||||
}
|
||||
},
|
||||
labelStyle: function() { // sets the class to apply to the label
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$( "#node-input-timeout" ).spinner({min:1,max:60});
|
||||
$( "#node-input-rate" ).spinner({min:1});
|
||||
|
||||
$( "#node-input-randomFirst" ).spinner({min:0});
|
||||
$( "#node-input-randomLast" ).spinner({min:1});
|
||||
|
||||
if (this.pauseType == "delay") {
|
||||
$("#delay-details").show();
|
||||
$("#rate-details").hide();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
} else if (this.pauseType == "rate") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").show();
|
||||
} else if (this.pauseType == "random") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").hide();
|
||||
$("#random-details").show();
|
||||
$("#node-input-dr").hide();
|
||||
} else if (this.pauseType == "queue") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
}
|
||||
|
||||
if (!this.timeoutUnits) {
|
||||
$("#node-input-timeoutUnits option").filter(function() {
|
||||
return $(this).val() == 'seconds';
|
||||
}).attr('selected', true);
|
||||
}
|
||||
|
||||
if (!this.randomUnits) {
|
||||
$("#node-input-randomUnits option").filter(function() {
|
||||
return $(this).val() == 'seconds';
|
||||
}).attr('selected', true);
|
||||
}
|
||||
|
||||
$("#node-input-pauseType").on("change",function() {
|
||||
if (this.value == "delay") {
|
||||
$("#delay-details").show();
|
||||
$("#rate-details").hide();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
} else if (this.value == "rate") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").show();
|
||||
} else if (this.value == "random") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").hide();
|
||||
$("#random-details").show();
|
||||
$("#node-input-dr").hide();
|
||||
} else if (this.value == "queue") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,192 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
//Simple node to introduce a pause into a flow
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
var MILLIS_TO_NANOS = 1000000;
|
||||
var SECONDS_TO_NANOS = 1000000000;
|
||||
|
||||
function DelayNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.pauseType = n.pauseType;
|
||||
this.timeoutUnits = n.timeoutUnits;
|
||||
this.randomUnits = n.randomUnits;
|
||||
this.rateUnits = n.rateUnits;
|
||||
|
||||
if (n.timeoutUnits === "milliseconds") {
|
||||
this.timeout = n.timeout;
|
||||
} else if (n.timeoutUnits === "seconds") {
|
||||
this.timeout = n.timeout * 1000;
|
||||
} else if (n.timeoutUnits === "minutes") {
|
||||
this.timeout = n.timeout * (60 * 1000);
|
||||
} else if (n.timeoutUnits === "hours") {
|
||||
this.timeout = n.timeout * (60 * 60 * 1000);
|
||||
} else if (n.timeoutUnits === "days") {
|
||||
this.timeout = n.timeout * (24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
if (n.rateUnits === "second") {
|
||||
this.rate = 1000/n.rate;
|
||||
} else if (n.rateUnits === "minute") {
|
||||
this.rate = (60 * 1000)/n.rate;
|
||||
} else if (n.rateUnits === "hour") {
|
||||
this.rate = (60 * 60 * 1000)/n.rate;
|
||||
} else if (n.rateUnits === "day") {
|
||||
this.rate = (24 * 60 * 60 * 1000)/n.rate;
|
||||
}
|
||||
|
||||
if (n.randomUnits === "milliseconds") {
|
||||
this.randomFirst = n.randomFirst * 1;
|
||||
this.randomLast = n.randomLast * 1;
|
||||
} else if (n.randomUnits === "seconds") {
|
||||
this.randomFirst = n.randomFirst * 1000;
|
||||
this.randomLast = n.randomLast * 1000;
|
||||
} else if (n.randomUnits === "minutes") {
|
||||
this.randomFirst = n.randomFirst * (60 * 1000);
|
||||
this.randomLast = n.randomLast * (60 * 1000);
|
||||
} else if (n.randomUnits === "hours") {
|
||||
this.randomFirst = n.randomFirst * (60 * 60 * 1000);
|
||||
this.randomLast = n.randomLast * (60 * 60 * 1000);
|
||||
} else if (n.randomUnits === "days") {
|
||||
this.randomFirst = n.randomFirst * (24 * 60 * 60 * 1000);
|
||||
this.randomLast = n.randomLast * (24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
this.diff = this.randomLast - this.randomFirst;
|
||||
this.name = n.name;
|
||||
this.idList = [];
|
||||
this.buffer = [];
|
||||
this.intervalID = -1;
|
||||
this.randomID = -1;
|
||||
this.lastSent = null;
|
||||
this.drop = n.drop;
|
||||
var node = this;
|
||||
|
||||
if (this.pauseType === "delay") {
|
||||
this.on("input", function(msg) {
|
||||
var id;
|
||||
id = setTimeout(function(){
|
||||
node.idList.splice(node.idList.indexOf(id),1);
|
||||
node.send(msg);
|
||||
}, node.timeout);
|
||||
this.idList.push(id);
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
for (var i=0; i<this.idList.length; i++ ) {
|
||||
clearTimeout(this.idList[i]);
|
||||
}
|
||||
this.idList = [];
|
||||
});
|
||||
|
||||
} else if (this.pauseType === "rate") {
|
||||
this.on("input", function(msg) {
|
||||
if (!node.drop) {
|
||||
if ( node.intervalID !== -1) {
|
||||
node.buffer.push(msg);
|
||||
if (node.buffer.length > 0) {
|
||||
node.status({text:node.buffer.length});
|
||||
}
|
||||
if (node.buffer.length > 1000) {
|
||||
node.warn(this.name + " buffer exceeded 1000 messages");
|
||||
}
|
||||
} else {
|
||||
node.send(msg);
|
||||
node.intervalID = setInterval(function() {
|
||||
if (node.buffer.length === 0) {
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = -1;
|
||||
node.status({text:""});
|
||||
}
|
||||
|
||||
if (node.buffer.length > 0) {
|
||||
node.send(node.buffer.shift());
|
||||
node.status({text:node.buffer.length});
|
||||
}
|
||||
},node.rate);
|
||||
}
|
||||
} else {
|
||||
var timeSinceLast;
|
||||
if (node.lastSent) {
|
||||
timeSinceLast = process.hrtime(node.lastSent);
|
||||
}
|
||||
if (!node.lastSent) { // ensuring that we always send the first message
|
||||
node.lastSent = process.hrtime();
|
||||
node.send(msg);
|
||||
} else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
|
||||
node.lastSent = process.hrtime();
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
clearInterval(this.intervalID);
|
||||
this.buffer = [];
|
||||
});
|
||||
|
||||
} else if (this.pauseType === "queue") {
|
||||
this.intervalID = setInterval(function() {
|
||||
if (node.buffer.length > 0) {
|
||||
node.send(node.buffer.shift()); // send the first on the queue
|
||||
}
|
||||
node.status({text:node.buffer.length});
|
||||
//console.log(node.buffer);
|
||||
},node.rate);
|
||||
|
||||
this.on("input", function(msg) {
|
||||
if (!msg.hasOwnProperty("topic")) { msg.topic = "_none_"; }
|
||||
var hit = false;
|
||||
for (var b in node.buffer) { // check if already in queue
|
||||
if (msg.topic === node.buffer[b].topic) {
|
||||
node.buffer[b] = msg; // if so - replace existing entry
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
if (!hit) { node.buffer.push(msg); } // if not add to end of queue
|
||||
node.status({text:node.buffer.length});
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
clearInterval(this.intervalID);
|
||||
this.buffer = [];
|
||||
node.status({text:node.buffer.length});
|
||||
});
|
||||
|
||||
} else if (this.pauseType === "random") {
|
||||
this.on("input", function(msg) {
|
||||
var wait = node.randomFirst + (node.diff * Math.random());
|
||||
var id = setTimeout(function(){
|
||||
node.idList.splice(node.idList.indexOf(id),1);
|
||||
node.send(msg);
|
||||
}, wait);
|
||||
this.idList.push(id);
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
for (var i=0; i<this.idList.length; i++ ) {
|
||||
clearTimeout(this.idList[i]);
|
||||
}
|
||||
this.idList = [];
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("delay",DelayNode);
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
<!--
|
||||
Copyright 2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="trigger">
|
||||
<div class="form-row">
|
||||
<label for="node-input-op1type"><i class="fa fa-arrow-up"></i> Output</label>
|
||||
<select id="node-input-op1type" style="width:73% !important">
|
||||
<option value="val">the value below</option>
|
||||
<option value="pay">the existing payload</option>
|
||||
<option value="nul">nothing (no output)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-op1">
|
||||
<label for="node-input-op1"> </label>
|
||||
<input type="text" id="node-input-op1">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-duration"><i class="fa fa-clock-o"></i> then wait</label>
|
||||
<input type="text" id="node-input-duration" placeholder="250" style="direction:rtl; width:70px !important">
|
||||
<select id="node-input-units" style="width:140px !important">
|
||||
<option value="ms">Milliseconds</option>
|
||||
<option value="s">Seconds</option>
|
||||
<option value="min">Minutes</option>
|
||||
<option value="hr">Hours</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-op2type"><i class="fa fa-arrow-down"></i> output</label>
|
||||
<select id="node-input-op2type" style="width:73% !important">
|
||||
<option value="val">the value below</option>
|
||||
<option value="pay">the existing payload</option>
|
||||
<option value="nul">nothing (no output)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-op2">
|
||||
<label for="node-input-op2"> </label>
|
||||
<input type="text" id="node-input-op2">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-extend"><i class="fa fa-repeat"></i> and</label>
|
||||
<select id="node-input-extend" style="width:73% !important">
|
||||
<option value="false">don't extend the timer if retriggered</option>
|
||||
<option value="true">extend the timer if retriggered</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<!-- <div class="form-tips">Tip: Outputs can be values, null, {{templated}} or msg.payload<br/> -->
|
||||
<div class="form-tips">Setting the timeout to 0 sets an infinite timeout = single shot.</div>
|
||||
<script>
|
||||
{
|
||||
$("#node-input-op1type").change(function() {
|
||||
if ($("#node-input-op1type").val() == "val") { $("#node-op1").show(); }
|
||||
else { $("#node-op1").hide(); }
|
||||
});
|
||||
$("#node-input-op2type").change(function() {
|
||||
if ($("#node-input-op2type").val() == "val") { $("#node-op2").show(); }
|
||||
else { $("#node-op2").hide(); }
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="trigger">
|
||||
<p>Creates two messages on the output separated by a timeout whenever ANY <b>msg</b> arrives on the input.</p>
|
||||
<p>For example, this can be used to toggle a Raspberry PI GPIO pin on and off.</p>
|
||||
<p>The two output states can be specified as can the duration of the timer.
|
||||
Either output can be set to a value, or templated from the inbound
|
||||
<b>msg</b> using mustache syntax. <pre>The payload is {{payload}}</pre></p>
|
||||
<p>If the payload is an object then setting the output to <i>existing payload</i> will pass the complete payload object through.</p>
|
||||
<p>Optionally the timer can be extended by being retriggered... or not.</p>
|
||||
<p>By setting the first output to <i>nothing</i>, and selecting extend timer - a watchdog timer can be created.
|
||||
No output will happen as long as repeated inputs occur within the timeout period.</p>
|
||||
<p>Setting the timer to 0 creates an "infinite" timeout - the first output will happen but the second
|
||||
never will, and neither can the first be retriggered - so a true one shot.</p>
|
||||
<p>If a <b>msg.reset</b> property is present any timeout currently in progress
|
||||
will be cleared and the second output will not happen.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('trigger',{
|
||||
category: 'function',
|
||||
color:"#E6E0F8",
|
||||
defaults: {
|
||||
op1: {value:"1"},
|
||||
op2: {value:"0"},
|
||||
op1type: {value:""},
|
||||
op2type: {value:""},
|
||||
duration: {value:"250",required:true,validate:RED.validators.number()},
|
||||
extend: {value:"false"},
|
||||
units: {value: "ms"},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "trigger.png",
|
||||
label: function() {
|
||||
if (this.duration > 0) {
|
||||
return this.name||"trigger "+this.duration+this.units;
|
||||
}
|
||||
else {
|
||||
return this.name||"trigger once ∞";
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$( "#node-input-duration" ).spinner({
|
||||
min:1,
|
||||
increment:25
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var mustache = require("mustache");
|
||||
function TriggerNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.op1 = n.op1 || "1";
|
||||
this.op2 = n.op2 || "0";
|
||||
this.op1type = n.op1type || "val";
|
||||
this.op2type = n.op2type || "val";
|
||||
this.extend = n.extend || false;
|
||||
this.units = n.units || "ms";
|
||||
this.duration = n.duration || 250;
|
||||
if (this.duration <= 0) { this.duration = 0; }
|
||||
else {
|
||||
if (this.units == "s") { this.duration = this.duration * 1000; }
|
||||
if (this.units == "min") { this.duration = this.duration * 1000 * 60; }
|
||||
if (this.units == "hr") { this.duration = this.duration * 1000 *60 * 60; }
|
||||
}
|
||||
this.op1Templated = this.op1.indexOf("{{") != -1;
|
||||
this.op2Templated = this.op2.indexOf("{{") != -1;
|
||||
if (!isNaN(this.op1)) { this.op1 = Number(this.op1); }
|
||||
if (!isNaN(this.op2)) { this.op2 = Number(this.op2); }
|
||||
if (this.op1 == "true") { this.op1 = true; }
|
||||
if (this.op2 == "true") { this.op2 = true; }
|
||||
if (this.op1 == "false") { this.op1 = false; }
|
||||
if (this.op2 == "false") { this.op2 = false; }
|
||||
if (this.op1 == "null") { this.op1 = null; }
|
||||
if (this.op2 == "null") { this.op2 = null; }
|
||||
try { this.op1 = JSON.parse(this.op1); }
|
||||
catch(e) { this.op1 = this.op1; }
|
||||
try { this.op2 = JSON.parse(this.op2); }
|
||||
catch(e) { this.op2 = this.op2; }
|
||||
|
||||
var node = this;
|
||||
var tout = null;
|
||||
var m2;
|
||||
this.on("input", function(msg) {
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
clearTimeout(tout);
|
||||
tout = null;
|
||||
}
|
||||
else {
|
||||
if (!tout) {
|
||||
if (node.op2type === "pay") { m2 = msg.payload; }
|
||||
else if (node.op2Templated) { m2 = mustache.render(node.op2,msg); }
|
||||
else { m2 = node.op2; }
|
||||
if (node.op1type === "pay") { }
|
||||
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
|
||||
else { msg.payload = node.op1; }
|
||||
if (node.op1type !== "nul") { node.send(msg); }
|
||||
if (node.duration === 0) { tout = "infinite"; }
|
||||
else {
|
||||
tout = setTimeout(function() {
|
||||
msg.payload = m2;
|
||||
if (node.op2type !== "nul") { node.send(msg); }
|
||||
tout = null;
|
||||
},node.duration);
|
||||
}
|
||||
}
|
||||
else if ((node.extend == "true") && (node.duration > 0)) {
|
||||
clearTimeout(tout);
|
||||
tout = setTimeout(function() {
|
||||
msg.payload = m2;
|
||||
if (node.op2type !== "nul") { node.send(msg); }
|
||||
tout = null;
|
||||
},node.duration);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.on("close", function() {
|
||||
if (tout) { clearTimeout(tout); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("trigger",TriggerNode);
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="comment">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-comment"></i> Title</label>
|
||||
<input type="text" id="node-input-name" placeholder="Comment">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom: 0px;">
|
||||
<label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> Body - will be rendered in info tab.</label>
|
||||
<input type="hidden" id="node-input-info" autofocus="autofocus">
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-info-editor" ></div>
|
||||
</div>
|
||||
<div class="form-tips">Tip: The text here can be styled as <i><a href="https://help.github.com/articles/markdown-basics/" target="_new">Github flavored Markdown</a></i></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="comment">
|
||||
<p>Comment</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('comment',{
|
||||
category: 'function',
|
||||
color:"#ffffff",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
info: {value:""}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
icon: "comment.png",
|
||||
label: function() {
|
||||
return this.name||"";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
info: function() {
|
||||
return "### "+this.name+"\n"+this.info;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$( "#node-input-outputs" ).spinner({
|
||||
min:1
|
||||
});
|
||||
function functionDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
};
|
||||
$( "#dialog" ).on("dialogresize", functionDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-function');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
functionDialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",functionDialogResize);
|
||||
});
|
||||
var that = this;
|
||||
require(["orion/editor/edit"], function(edit) {
|
||||
that.editor = edit({
|
||||
parent:document.getElementById('node-input-info-editor'),
|
||||
lang:"text",
|
||||
showLinesRuler:false,
|
||||
showFoldingRuler:false,
|
||||
contents: $("#node-input-info").val()
|
||||
});
|
||||
$("#node-input-name").focus();
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
$("#node-input-info").val(this.editor.getText());
|
||||
delete this.editor;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,51 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="unknown">
|
||||
<div class="form-tips"><p>This node is a type unknown to your installation of Node-RED.</p>
|
||||
<p><i>If you deploy with the node in this state, it's configuration will be preserved, but
|
||||
the flow will not start until the missing type is installed.</i></p>
|
||||
<p>See the Info side bar for more help</p></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="unknown">
|
||||
<p>This node is a type unknown to your installation of Node-RED.</p>
|
||||
<p><i>If you deploy with the node in this state, it's configuration will be preserved, but
|
||||
the flow will not start until the missing type is installed.</i></p>
|
||||
<p>It is possible this node type is already installed, but is missing a dependency. Check the Node-RED start-up log for
|
||||
any error messages associated with the missing node type. Use <b>npm install <module></b> to install any missing modules
|
||||
and restart Node-RED and reimport the nodes.</p>
|
||||
<p>Otherwise, you should contact the author of the flow to obtain a copy of the missing node type.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('unknown',{
|
||||
category: 'unknown',
|
||||
color:"#fff0f0",
|
||||
defaults: {
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "",
|
||||
label: function() {
|
||||
return "("+this.name+")"||"unknown";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return "node_label_unknown";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,171 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<script type="text/x-red" data-template-name="arduino in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-arduino"><i class="fa fa-tasks"></i> Arduino</label>
|
||||
<input type="text" id="node-input-arduino">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
|
||||
<input type="text" id="node-input-pin" placeholder="2">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-state"><i class="fa fa-wrench"></i> Type</label>
|
||||
<select type="text" id="node-input-state" style="width: 150px;">
|
||||
<option value="INPUT">Digital pin</option>
|
||||
<option value="ANALOG">Analogue pin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips"><b>Note:</b> You cannot use the same pin for both output and input.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="arduino in">
|
||||
<p>Arduino input node. Connects to local Arduino and monitors the selected pin for changes. Uses <a href="http://firmata.org/" target="_new"><i>Firmata</i>.</a></p>
|
||||
<p>The Arduino must be loaded with the Standard Firmata sketch available in the Arduino examples.</p>
|
||||
<p>You can select either Digital or Analogue input. Outputs the value read as <b>msg.payload</b> and the pin number as <b>msg.topic</b>.</p>
|
||||
<p>It only outputs on a change of value - fine for digital inputs, but you can get a lot of data from analogue pins which you must then handle.</p>
|
||||
<p>You can set the sample rate in ms from 20 to 65535.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('arduino in',{
|
||||
category: 'Arduino',
|
||||
color:"#3fadb5",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
pin: {value:"",required:true},
|
||||
state: {value:"INPUT",required:true},
|
||||
arduino: {type:"arduino-board"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "arduino.png",
|
||||
label: function() {
|
||||
var a = "";
|
||||
if (this.state == "ANALOG") a = "A";
|
||||
return this.name||"Pin: "+a+this.pin;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="arduino out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-arduino"><i class="fa fa-tasks"></i> Arduino</label>
|
||||
<input type="text" id="node-input-arduino">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
|
||||
<input type="text" id="node-input-pin" placeholder="13">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-state"><i class="fa fa-wrench"></i> Type</label>
|
||||
<select type="text" id="node-input-state" style="width: 200px;">
|
||||
<option value="OUTPUT">Digital (0/1)</option>
|
||||
<option value="PWM">Analogue (0-255)</option>
|
||||
<option value="SERVO">Servo (0-180)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips"><b>Note:</b> You cannot use the same pin for both output and input.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="arduino out">
|
||||
<p>Arduino output node. Connects to local Arduino and writes to the selected digital pin. Uses <a href="http://firmata.org/" target="_new"><i>Firmata</i>.</a></p>
|
||||
<p>The Arduino must be loaded with the Standard Firmata sketch available in the Arduino examples.</p>
|
||||
<p>You can select Digital, Analogue (PWM) or Servo type outputs. Expects a numeric value in <b>msg.payload</b>. The pin number is set in the properties panel.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('arduino out',{
|
||||
category: 'Arduino',
|
||||
color:"#3fadb5",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
pin: {value:"",required:true},
|
||||
state: {value:"",required:true},
|
||||
arduino: {type:"arduino-board"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "arduino.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||"Pin: "+this.pin;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="arduino-board">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-device"><i class="fa fa-random"></i> Port</label>
|
||||
<input type="text" id="node-config-input-device" style="width:60%;" placeholder="e.g. /dev/ttyUSB0 COM1"/>
|
||||
<a id="node-config-lookup-serial" class="btn"><i id="node-config-lookup-serial-icon" class="fa fa-search"></i></a>
|
||||
</div>
|
||||
<div class="form-tips"><b>Tip:</b> Use search to try to auto-detect serial port.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('arduino-board',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
device: {value:"",required:true}
|
||||
},
|
||||
label: function() {
|
||||
return this.device||"arduino";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
try {
|
||||
$("#node-config-input-device").autocomplete( "destroy" );
|
||||
} catch(err) { }
|
||||
$("#node-config-lookup-serial").click(function() {
|
||||
$("#node-config-lookup-serial-icon").removeClass('fa-search');
|
||||
$("#node-config-lookup-serial-icon").addClass('spinner');
|
||||
$("#node-config-lookup-serial").addClass('disabled');
|
||||
|
||||
$.getJSON('arduinoports',function(data) {
|
||||
$("#node-config-lookup-serial-icon").addClass('fa-search');
|
||||
$("#node-config-lookup-serial-icon").removeClass('spinner');
|
||||
$("#node-config-lookup-serial").removeClass('disabled');
|
||||
var ports = [];
|
||||
$.each(data, function(i, port){
|
||||
ports.push(port);
|
||||
});
|
||||
$("#node-config-input-device").autocomplete({
|
||||
source:ports,
|
||||
minLength:0,
|
||||
close: function( event, ui ) {
|
||||
$("#node-config-input-device").autocomplete( "destroy" );
|
||||
}
|
||||
}).autocomplete("search","");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,158 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var ArduinoFirmata = require('arduino-firmata');
|
||||
var fs = require('fs');
|
||||
var plat = require('os').platform();
|
||||
var portlist = ArduinoFirmata.list(function (err, ports) {
|
||||
portlist = ports;
|
||||
});
|
||||
|
||||
// The Board Definition - this opens (and closes) the connection
|
||||
function ArduinoNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.device = n.device || null;
|
||||
this.repeat = n.repeat||25;
|
||||
//node.log("opening connection "+this.device);
|
||||
var node = this;
|
||||
node.board = new ArduinoFirmata();
|
||||
if (portlist.indexOf(node.device) === -1) {
|
||||
node.warn("Device "+node.device+" not found");
|
||||
}
|
||||
else {
|
||||
node.board.connect(node.device);
|
||||
}
|
||||
|
||||
node.board.on('boardReady', function(){
|
||||
node.log("version "+node.board.boardVersion);
|
||||
});
|
||||
|
||||
node.on('close', function(done) {
|
||||
if (node.board) {
|
||||
try {
|
||||
node.board.close(function() {
|
||||
done();
|
||||
node.log("port closed");
|
||||
});
|
||||
} catch(e) { done(); }
|
||||
} else { done(); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("arduino-board",ArduinoNode);
|
||||
|
||||
|
||||
// The Input Node
|
||||
function DuinoNodeIn(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.buttonState = -1;
|
||||
this.pin = n.pin;
|
||||
this.state = n.state;
|
||||
this.arduino = n.arduino;
|
||||
this.serverConfig = RED.nodes.getNode(this.arduino);
|
||||
if (typeof this.serverConfig === "object") {
|
||||
this.board = this.serverConfig.board;
|
||||
//this.repeat = this.serverConfig.repeat;
|
||||
var node = this;
|
||||
node.status({fill:"red",shape:"ring",text:"connecting"});
|
||||
|
||||
node.board.on('connect', function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
//console.log("i",node.state,node.pin);
|
||||
if (node.state == "ANALOG") {
|
||||
node.board.on('analogChange', function(e) {
|
||||
if (e.pin == node.pin) {
|
||||
var msg = {payload:e.value, topic:"A"+e.pin};
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
node.board.pinMode(node.pin, ArduinoFirmata.INPUT);
|
||||
node.board.on('digitalChange', function(e) {
|
||||
if (e.pin == node.pin) {
|
||||
var msg = {payload:e.value, topic:e.pin};
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
util.log("[Firmata-arduino] port not configured");
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("arduino in",DuinoNodeIn);
|
||||
|
||||
|
||||
// The Output Node
|
||||
function DuinoNodeOut(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.buttonState = -1;
|
||||
this.pin = n.pin;
|
||||
this.state = n.state;
|
||||
this.arduino = n.arduino;
|
||||
this.serverConfig = RED.nodes.getNode(this.arduino);
|
||||
if (typeof this.serverConfig === "object") {
|
||||
this.board = this.serverConfig.board;
|
||||
var node = this;
|
||||
node.status({fill:"red",shape:"ring",text:"connecting"});
|
||||
|
||||
node.board.on('connect', function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
//console.log("o",node.state,node.pin);
|
||||
node.board.pinMode(node.pin, node.state);
|
||||
node.on("input", function(msg) {
|
||||
if (node.state == "OUTPUT") {
|
||||
if ((msg.payload == true)||(msg.payload == 1)||(msg.payload.toString().toLowerCase() == "on")) {
|
||||
node.board.digitalWrite(node.pin, true);
|
||||
}
|
||||
if ((msg.payload == false)||(msg.payload == 0)||(msg.payload.toString().toLowerCase() == "off")) {
|
||||
node.board.digitalWrite(node.pin, false);
|
||||
}
|
||||
}
|
||||
if (node.state == "PWM") {
|
||||
msg.payload = msg.payload * 1;
|
||||
if ((msg.payload >= 0) && (msg.payload <= 255)) {
|
||||
//console.log(msg.payload, node.pin);
|
||||
node.board.analogWrite(node.pin, msg.payload);
|
||||
}
|
||||
}
|
||||
if (node.state == "SERVO") {
|
||||
msg.payload = msg.payload * 1;
|
||||
if ((msg.payload >= 0) && (msg.payload <= 180)) {
|
||||
//console.log(msg.payload, node.pin);
|
||||
node.board.servoWrite(node.pin, msg.payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
util.log("[Firmata-arduino] port not configured");
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("arduino out",DuinoNodeOut);
|
||||
|
||||
RED.httpAdmin.get("/arduinoports", RED.auth.needsPermission("arduino.read"), function(req,res) {
|
||||
ArduinoFirmata.list(function (err, ports) {
|
||||
res.json(ports);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="rpi-gpio in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
|
||||
<select type="text" id="node-input-pin" style="width: 250px;">
|
||||
<option value='' disabled selected style='display:none;'>select pin</option>
|
||||
<option value="3">3 - SDA1 </option>
|
||||
<option value="5">5 - SCL1 </option>
|
||||
<option value="7">7 - GPIO7</option>
|
||||
<option value="8">8 - TxD </option>
|
||||
<option value="10">10 - RxD </option>
|
||||
<option value="11">11 - GPIO0</option>
|
||||
<option value="12">12 - GPIO1</option>
|
||||
<option value="13">13 - GPIO2</option>
|
||||
<option value="15">15 - GPIO3</option>
|
||||
<option value="16">16 - GPIO4</option>
|
||||
<option value="18">18 - GPIO5</option>
|
||||
<option value="19">19 - MOSI </option>
|
||||
<option value="21">21 - MISO </option>
|
||||
<option value="22">22 - GPIO6</option>
|
||||
<option value="23">23 - SCLK </option>
|
||||
<option value="24">24 - CE0 </option>
|
||||
<option value="26">26 - CE1 </option>
|
||||
</select>
|
||||
<span id="pitype"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-intype"><i class="fa fa-level-up"></i> Resistor ?</label>
|
||||
<select type="text" id="node-input-intype" style="width: 150px;">
|
||||
<option value="tri">none</option>
|
||||
<option value="up">pullup</option>
|
||||
<option value="down">pulldown</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-read" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-read" style="width: 70%;">Read initial state of pin on deploy/restart ?</label>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
|
||||
<div class="form-tips">Tip: Only Digital Input is supported - input must be 0 or 1.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-gpio in">
|
||||
<p>Raspberry Pi input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin. Requires the gpio command to work.</p>
|
||||
<p>You may also enable the input pullup resistor or the pulldown resistor.</p>
|
||||
<p>The <b>msg.topic</b> is set to <i>pi/{the pin number}</i></p>
|
||||
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
|
||||
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var pinsInUse = {};
|
||||
RED.nodes.registerType('rpi-gpio in',{
|
||||
category: 'Raspberry Pi',
|
||||
color:"#c6dbef",
|
||||
defaults: {
|
||||
name: { value:"" },
|
||||
pin: { value:"",required:true,validate:RED.validators.number() },
|
||||
intype: { value: "in" },
|
||||
read: { value:false }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "rpi.png",
|
||||
info: function() {
|
||||
if ( Object.keys(pinsInUse).length !== 0 ) {
|
||||
return "**Pins in use** : "+Object.keys(pinsInUse);
|
||||
}
|
||||
else { return ""; }
|
||||
},
|
||||
label: function() {
|
||||
return this.name||"Pin: "+this.pin ;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var pinnow = this.pin;
|
||||
$.getJSON('rpi-gpio/'+this.id,function(data) {
|
||||
$('#pitype').text(data.type);
|
||||
if ((data.type === "Model B+") || (data.type === "Model A+")) {
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO22"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO26"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO23"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO24"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO27"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO25"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO28"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO29"));
|
||||
$('#node-input-pin').val(pinnow);
|
||||
}
|
||||
});
|
||||
|
||||
$.getJSON('rpi-pins/'+this.id,function(data) {
|
||||
pinsInUse = data || {};
|
||||
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data));
|
||||
});
|
||||
|
||||
$("#node-input-pin").change(function() {
|
||||
var pinnew = $("#node-input-pin").val();
|
||||
if ((pinnew) && (pinnew !== pinnow)) {
|
||||
if (pinsInUse.hasOwnProperty(pinnew)) {
|
||||
RED.notify("Pin "+pinnew+" already in use.","warn");
|
||||
}
|
||||
pinnow = pinnew;
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-intype").change(function() {
|
||||
var newtype = $("#node-input-intype option:selected").val();
|
||||
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
|
||||
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="rpi-gpio out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
|
||||
<select type="text" id="node-input-pin" style="width: 250px;">
|
||||
<option value='' disabled selected style='display:none;'>select pin</option>
|
||||
<option value="3">3 - SDA1 </option>
|
||||
<option value="5">5 - SCL1 </option>
|
||||
<option value="7">7 - GPIO7</option>
|
||||
<option value="8">8 - TxD </option>
|
||||
<option value="10">10 - RxD </option>
|
||||
<option value="11">11 - GPIO0</option>
|
||||
<option value="12">12 - GPIO1</option>
|
||||
<option value="13">13 - GPIO2</option>
|
||||
<option value="15">15 - GPIO3</option>
|
||||
<option value="16">16 - GPIO4</option>
|
||||
<option value="18">18 - GPIO5</option>
|
||||
<option value="19">19 - MOSI </option>
|
||||
<option value="21">21 - MISO </option>
|
||||
<option value="22">22 - GPIO6</option>
|
||||
<option value="23">23 - SCLK </option>
|
||||
<option value="24">24 - CE0 </option>
|
||||
<option value="26">26 - CE1 </option>
|
||||
</select>
|
||||
<span id="pitype"></span>
|
||||
</div>
|
||||
<div class="form-row" id="node-set-pwm">
|
||||
<label> Type</label>
|
||||
<select id="node-input-out" style="width: 250px;">
|
||||
<option value="out">Digital output</option>
|
||||
<option value="pwm">PWM output</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-set-tick">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-set" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-set" style="width: 70%;">Initialise pin state ?</label>
|
||||
</div>
|
||||
<div class="form-row" id="node-set-state">
|
||||
<label for="node-input-level"> </label>
|
||||
<select id="node-input-level" style="width: 250px;">
|
||||
<option value="0">initial level of pin - low (0)</option>
|
||||
<option value="1">initial level of pin - high (1)</option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
|
||||
<div class="form-tips" id="dig-tip"><b>Tip</b>: For digital output - input must be 0 or 1.</div>
|
||||
<div class="form-tips" id="pwm-tip"><b>Tip</b>: For PWM output - input must be between 0 to 1023.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-gpio out">
|
||||
<p>Raspberry Pi output node. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false). Requires the gpio command to work.</p>
|
||||
<p>Will set the selected physical pin high or low depending on the value passed in.</p>
|
||||
<p>The initial value of the pin at deploy time can also be set to 0 or 1.</p>
|
||||
<p>When using PWM mode - expects an input value of a number 0 - 100.</p>
|
||||
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
|
||||
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var pinsInUse = {};
|
||||
RED.nodes.registerType('rpi-gpio out',{
|
||||
category: 'Raspberry Pi',
|
||||
color:"#c6dbef",
|
||||
defaults: {
|
||||
name: { value:"" },
|
||||
pin: { value:"",required:true,validate:RED.validators.number() },
|
||||
set: { value:"" },
|
||||
level: { value:"0" },
|
||||
out: { value:"out" }
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "rpi.png",
|
||||
info: function() {
|
||||
if ( Object.keys(pinsInUse).length !== 0 ) {
|
||||
return "**Pins in use** : "+Object.keys(pinsInUse);
|
||||
}
|
||||
else { return ""; }
|
||||
},
|
||||
align: "right",
|
||||
label: function() {
|
||||
if (this.out === "pwm") { return this.name || "PWM: "+this.pin; }
|
||||
else { return this.name||"Pin: "+this.pin ; }
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var pinnow = this.pin;
|
||||
if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); }
|
||||
$.getJSON('rpi-gpio/'+this.id,function(data) {
|
||||
$('#pitype').text(data.type);
|
||||
if ((data.type === "Model B+") || (data.type === "Model A+")) {
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO22"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO26"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO23"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO24"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO27"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO25"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO28"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO29"));
|
||||
$('#node-input-pin').val(pinnow);
|
||||
}
|
||||
});
|
||||
|
||||
$.getJSON('rpi-pins/'+this.id,function(data) {
|
||||
pinsInUse = data || {};
|
||||
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data));
|
||||
});
|
||||
|
||||
$("#node-input-pin").change(function() {
|
||||
var pinnew = $("#node-input-pin").val();
|
||||
if ((pinnew) && (pinnew !== pinnow)) {
|
||||
if (pinsInUse.hasOwnProperty(pinnew)) {
|
||||
RED.notify("Pin "+pinnew+" already in use.","warn");
|
||||
}
|
||||
pinnow = pinnew;
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-out").change(function() {
|
||||
var newtype = $("#node-input-out option:selected").val();
|
||||
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
|
||||
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
|
||||
}
|
||||
});
|
||||
|
||||
var hidestate = function () {
|
||||
if ($("#node-input-out").val() === "pwm") {
|
||||
$('#node-set-tick').hide();
|
||||
$('#node-set-state').hide();
|
||||
$('#node-input-set').prop('checked', false);
|
||||
$("#dig-tip").hide();
|
||||
$("#pwm-tip").show();
|
||||
}
|
||||
else {
|
||||
$('#node-set-tick').show();
|
||||
$("#dig-tip").show();
|
||||
$("#pwm-tip").hide();
|
||||
}
|
||||
};
|
||||
$("#node-input-out").change(function () { hidestate(); });
|
||||
hidestate();
|
||||
|
||||
var setstate = function () {
|
||||
if ($('#node-input-set').is(":checked")) {
|
||||
$("#node-set-state").show();
|
||||
} else {
|
||||
$("#node-set-state").hide();
|
||||
}
|
||||
};
|
||||
$("#node-input-set").change(function () { setstate(); });
|
||||
setstate();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="rpi-mouse">
|
||||
<div class="form-row">
|
||||
<label for="node-input-butt"><i class="fa fa-circle"></i> Button</label>
|
||||
<select type="text" id="node-input-butt" style="width: 250px;">
|
||||
<option value="1">left</option>
|
||||
<option value="2">right</option>
|
||||
<option value="4">middle</option>
|
||||
<option value="7">any</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-mouse">
|
||||
<p>Raspberry Pi mouse button node. Generates a <b>msg.payload</b> with
|
||||
either a 1 or 0 when the selected mouse button is pressed and released</p>
|
||||
<p>Also sets <b>msg.button</b> to the code value, 1 = left, 2 = right, 4 = middle,
|
||||
so you can work out which button or combination was pressed.</p>
|
||||
<p>And sets <b>msg.topic</b> to <i>pi/mouse</i>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('rpi-mouse',{
|
||||
category: 'Raspberry Pi',
|
||||
color:"#c6dbef",
|
||||
defaults: {
|
||||
name: { value:"" },
|
||||
butt: { value:"1",required:true }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "rpi.png",
|
||||
label: function() {
|
||||
var na = "Pi Mouse";
|
||||
if (this.butt === "1") { na += " Left"; }
|
||||
if (this.butt === "2") { na += " Right"; }
|
||||
if (this.butt === "4") { na += " Middle"; }
|
||||
return this.name||na;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,284 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var fs = require('fs');
|
||||
|
||||
var gpioCommand = __dirname+'/nrgpio';
|
||||
|
||||
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
|
||||
//util.log("Info : Ignoring Raspberry Pi specific node.");
|
||||
throw "Info : Ignoring Raspberry Pi specific node.";
|
||||
}
|
||||
|
||||
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
|
||||
util.log("[rpi-gpio] Info : Can't find Pi RPi.GPIO python library.");
|
||||
throw "Warning : Can't find Pi RPi.GPIO python library.";
|
||||
}
|
||||
|
||||
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
|
||||
util.log("[rpi-gpio] Error : "+gpioCommand+" needs to be executable.");
|
||||
throw "Error : nrgpio must to be executable.";
|
||||
}
|
||||
|
||||
// the magic to make python print stuff immediately
|
||||
process.env.PYTHONUNBUFFERED = 1;
|
||||
|
||||
var pinsInUse = {};
|
||||
var pinTypes = {"out":"digital output", "tri":"input", "up":"input with pull up", "down":"input with pull down", "pwm":"PWM output"};
|
||||
|
||||
function GPIOInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.buttonState = -1;
|
||||
this.pin = n.pin;
|
||||
this.intype = n.intype;
|
||||
this.read = n.read || false;
|
||||
if (this.read) { this.buttonState = -2; }
|
||||
var node = this;
|
||||
if (!pinsInUse.hasOwnProperty(this.pin)) {
|
||||
pinsInUse[this.pin] = this.intype;
|
||||
}
|
||||
else {
|
||||
if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
|
||||
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.pin !== undefined) {
|
||||
if (node.intype === "tri") {
|
||||
node.child = spawn(gpioCommand, ["in",node.pin]);
|
||||
} else {
|
||||
node.child = spawn(gpioCommand, ["in",node.pin,node.intype]);
|
||||
}
|
||||
node.running = true;
|
||||
node.status({fill:"green",shape:"dot",text:"OK"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
data = data.toString().trim();
|
||||
if (data.length > 0) {
|
||||
if (node.buttonState !== -1) {
|
||||
node.send({ topic:"pi/"+node.pin, payload:Number(data) });
|
||||
}
|
||||
node.buttonState = data;
|
||||
node.status({fill:"green",shape:"dot",text:data});
|
||||
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
|
||||
}
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
if (RED.settings.verbose) { node.log("closed"); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
|
||||
else { node.log('error: ' + err); }
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
node.warn("Invalid GPIO pin: "+node.pin);
|
||||
}
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"close"});
|
||||
delete pinsInUse[node.pin];
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.stdin.write(" close "+node.pin);
|
||||
node.child.kill('SIGKILL');
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
|
||||
|
||||
|
||||
function GPIOOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.pin = n.pin;
|
||||
this.set = n.set || false;
|
||||
this.level = n.level || 0;
|
||||
this.out = n.out || "out";
|
||||
var node = this;
|
||||
if (!pinsInUse.hasOwnProperty(this.pin)) {
|
||||
pinsInUse[this.pin] = this.out;
|
||||
}
|
||||
else {
|
||||
if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
|
||||
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
|
||||
}
|
||||
}
|
||||
|
||||
function inputlistener(msg) {
|
||||
if (msg.payload === "true") { msg.payload = true; }
|
||||
if (msg.payload === "false") { msg.payload = false; }
|
||||
var out = Number(msg.payload);
|
||||
var limit = 1;
|
||||
if (node.out === "pwm") { limit = 100; }
|
||||
if ((out >= 0) && (out <= limit)) {
|
||||
if (RED.settings.verbose) { node.log("out: "+msg.payload); }
|
||||
if (node.child !== null) {
|
||||
node.child.stdin.write(msg.payload+"\n");
|
||||
node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
|
||||
}
|
||||
else {
|
||||
node.error("nrpgio python command not running");
|
||||
node.status({fill:"red",shape:"ring",text:"not running"});
|
||||
}
|
||||
}
|
||||
else { node.warn("Invalid input: "+out); }
|
||||
}
|
||||
|
||||
if (node.pin !== undefined) {
|
||||
if (node.set && (node.out === "out")) {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
|
||||
} else {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin]);
|
||||
}
|
||||
node.running = true;
|
||||
node.status({fill:"green",shape:"dot",text:"OK"});
|
||||
|
||||
node.on("input", inputlistener);
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
if (RED.settings.verbose) { node.log("closed"); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
|
||||
else { node.log('error: ' + err); }
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
node.warn("Invalid GPIO pin: "+node.pin);
|
||||
}
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"close"});
|
||||
delete pinsInUse[node.pin];
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.stdin.write(" close "+node.pin);
|
||||
node.child.kill('SIGKILL');
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var pitype = { type:"" };
|
||||
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
|
||||
if (err) {
|
||||
console.log('[rpi-gpio] Version command failed for some reason.');
|
||||
}
|
||||
else {
|
||||
if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
|
||||
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
|
||||
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
|
||||
else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; }
|
||||
else { console.log("SAW Pi TYPE",stdout.trim()); }
|
||||
}
|
||||
});
|
||||
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
|
||||
|
||||
function PiMouseNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.butt = n.butt || 7;
|
||||
var node = this;
|
||||
|
||||
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
|
||||
node.status({fill:"green",shape:"dot",text:"OK"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
data = Number(data);
|
||||
if (data === 0) { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
|
||||
else { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
if (RED.settings.verbose) { node.log("closed"); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio ommand not executable'); }
|
||||
else { node.log('error: ' + err); }
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"close"});
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.kill('SIGINT');
|
||||
node.child = null;
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rpi-mouse",PiMouseNode);
|
||||
|
||||
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
|
||||
res.json(pitype);
|
||||
});
|
||||
|
||||
RED.httpAdmin.get('/rpi-pins/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
|
||||
res.json(pinsInUse);
|
||||
});
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="mqtt in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label>
|
||||
<input type="text" id="node-input-broker">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mqtt in">
|
||||
<p>MQTT input node. Connects to a broker and subscribes to the specified topic. The topic may contain MQTT wildcards.</p>
|
||||
<p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.qos</b> and <b>msg.retain</b>.</p>
|
||||
<p><b>msg.payload</b> is a String.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mqtt in',{
|
||||
category: 'input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:"",required:true},
|
||||
broker: {type:"mqtt-broker", required:true}
|
||||
},
|
||||
color:"#d8bfd8",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "bridge.png",
|
||||
label: function() {
|
||||
return this.name||this.topic||"mqtt";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="mqtt out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label>
|
||||
<input type="text" id="node-input-broker">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-qos"><i class="fa fa-empire"></i> QoS</label>
|
||||
<select id="node-input-qos" style="width:125px !important">
|
||||
<option value=""></option>
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
<i class="fa fa-history"></i> Retain <select id="node-input-retain" style="width:125px !important">
|
||||
<option value=""></option>
|
||||
<option value="false">false</option>
|
||||
<option value="true">true</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: Leave topic, qos or retain blank if you want to set them via msg properties.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mqtt out">
|
||||
<p>Connects to a MQTT broker and publishes <b>msg.payload</b> either to the <b>msg.topic</b> or to the topic specified in the edit window. The value in the edit window has precedence.</p>
|
||||
<p>Likewise QoS and/or retain values in the edit panel will overwrite any <b>msg.qos</b> and <b>msg.retain</b> properties. If nothing is set they default to <i>0</i> and <i>false</i> respectively.</p>
|
||||
<p>If <b>msg.payload</b> contains an object it will be stringified before being sent.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mqtt out',{
|
||||
category: 'output',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:""},
|
||||
qos: {value:""},
|
||||
retain: {value:""},
|
||||
broker: {type:"mqtt-broker", required:true}
|
||||
},
|
||||
color:"#d8bfd8",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "bridge.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||this.topic||"mqtt";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="mqtt-broker">
|
||||
<div class="form-row node-input-broker">
|
||||
<label for="node-config-input-broker"><i class="fa fa-globe"></i> Broker</label>
|
||||
<input class="input-append-left" type="text" id="node-config-input-broker" placeholder="localhost" style="width: 40%;" >
|
||||
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
|
||||
<input type="text" id="node-config-input-port" placeholder="Port" style="width:45px">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> Client ID</label>
|
||||
<input type="text" id="node-config-input-clientid" placeholder="Leave blank for auto generated">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-user"><i class="fa fa-user"></i> Username</label>
|
||||
<input type="text" id="node-config-input-user">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
|
||||
<input type="password" id="node-config-input-password">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mqtt-broker',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
broker: {value:"",required:true},
|
||||
port: {value:1883,required:true,validate:RED.validators.number()},
|
||||
clientid: { value:"" }
|
||||
},
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
},
|
||||
label: function() {
|
||||
if (this.broker == "") { this.broker = "localhost"; }
|
||||
return (this.clientid?this.clientid+"@":"")+this.broker+":"+this.port;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var connectionPool = require("./lib/mqttConnectionPool");
|
||||
var isUtf8 = require('is-utf8');
|
||||
|
||||
function MQTTBrokerNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.broker = n.broker;
|
||||
this.port = n.port;
|
||||
this.clientid = n.clientid;
|
||||
if (this.credentials) {
|
||||
this.username = this.credentials.user;
|
||||
this.password = this.credentials.password;
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
}
|
||||
});
|
||||
|
||||
function MQTTInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.topic = n.topic;
|
||||
this.broker = n.broker;
|
||||
this.brokerConfig = RED.nodes.getNode(this.broker);
|
||||
if (this.brokerConfig) {
|
||||
this.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
|
||||
var node = this;
|
||||
this.client.subscribe(this.topic,2,function(topic,payload,qos,retain) {
|
||||
if (isUtf8(payload)) { payload = payload.toString(); }
|
||||
var msg = {topic:topic,payload:payload,qos:qos,retain:retain};
|
||||
if ((node.brokerConfig.broker === "localhost")||(node.brokerConfig.broker === "127.0.0.1")) {
|
||||
msg._topic = topic;
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
this.client.on("connectionlost",function() {
|
||||
node.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
});
|
||||
this.client.on("connect",function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
this.client.connect();
|
||||
} else {
|
||||
this.error("missing broker configuration");
|
||||
}
|
||||
this.on('close', function() {
|
||||
if (this.client) {
|
||||
this.client.disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mqtt in",MQTTInNode);
|
||||
|
||||
function MQTTOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.topic = n.topic;
|
||||
this.qos = n.qos || null;
|
||||
this.retain = n.retain;
|
||||
this.broker = n.broker;
|
||||
this.brokerConfig = RED.nodes.getNode(this.broker);
|
||||
|
||||
if (this.brokerConfig) {
|
||||
this.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
|
||||
var node = this;
|
||||
this.on("input",function(msg) {
|
||||
if (msg.qos) {
|
||||
msg.qos = parseInt(msg.qos);
|
||||
if ((msg.qos !== 0) && (msg.qos !== 1) && (msg.qos !== 2)) {
|
||||
msg.qos = null;
|
||||
}
|
||||
}
|
||||
msg.qos = Number(node.qos || msg.qos || 0);
|
||||
msg.retain = node.retain || msg.retain || false;
|
||||
msg.retain = ((msg.retain === true) || (msg.retain === "true")) || false;
|
||||
if (node.topic) {
|
||||
msg.topic = node.topic;
|
||||
}
|
||||
if ((msg.hasOwnProperty("topic")) && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
|
||||
this.client.publish(msg); // send the message
|
||||
}
|
||||
else { node.warn("Invalid topic specified"); }
|
||||
});
|
||||
this.client.on("connectionlost",function() {
|
||||
node.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
});
|
||||
this.client.on("connect",function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
this.client.connect();
|
||||
} else {
|
||||
this.error("missing broker configuration");
|
||||
}
|
||||
this.on('close', function() {
|
||||
if (this.client) {
|
||||
this.client.disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mqtt out",MQTTOutNode);
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="http in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-method"><i class="fa fa-tasks"></i> Method</label>
|
||||
<select type="text" id="node-input-method" style="width:72%;">
|
||||
<option value="get">GET</option>
|
||||
<option value="post">POST</option>
|
||||
<option value="put">PUT</option>
|
||||
<option value="delete">DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-url"><i class="fa fa-globe"></i> url</label>
|
||||
<input type="text" id="node-input-url" placeholder="/url">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div id="node-input-tip" class="form-tips">The url will be relative to <code><span id="node-input-path"></span></code>.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="http in">
|
||||
<p>Provides an input node for http requests, allowing the creation of simple web services.</p>
|
||||
<p>The resulting message has the following properties:
|
||||
<ul>
|
||||
<li>msg.req : <a href="http://expressjs.com/api.html#req">http request</a></li>
|
||||
<li>msg.res : <a href="http://expressjs.com/api.html#res">http response</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>For POST/PUT requests, the body is available under <code>msg.req.body</code>. This
|
||||
uses the <a href="http://expressjs.com/api.html#bodyParser">Express bodyParser middleware</a> to parse the content to a JSON object.
|
||||
</p>
|
||||
<p>
|
||||
By default, this expects the body of the request to be url encoded:
|
||||
<pre>foo=bar&this=that</pre>
|
||||
</p>
|
||||
<p>
|
||||
To send JSON encoded data to the node, the content-type header of the request must be set to
|
||||
<code>application/json</code>.
|
||||
</p>
|
||||
<p>
|
||||
<b>Note: </b>This node does not send any response to the http request. This should be done with
|
||||
a subsequent HTTP Response node, or Function node.
|
||||
In the case of a Function node, the <a href="http://expressjs.com/api.html#res">Express response documentation</a>
|
||||
describes how this should be done. For example:
|
||||
<pre>msg.res.send(200, 'Thanks for the request ');<br/>return msg;</pre>
|
||||
</p>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="http response">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">The messages sent to this node <b>must</b> originate from an <i>http input</i> node</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="http response">
|
||||
<p>Sends responses back to http requests received from an HTTP Input node.</p>
|
||||
<p>The response can be customised using the following message properties:</p>
|
||||
<ul>
|
||||
<li><code>payload</code> is sent as the body of the response</li>
|
||||
<li><code>statusCode</code>, if set, is used as the response status code (default: 200)</li>
|
||||
<li><code>headers</code>, if set, should be an object containing field/value
|
||||
pairs to be added as response headers.</li>
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="http request">
|
||||
<div class="form-row">
|
||||
<label for="node-input-method"><i class="fa fa-tasks"></i> Method</label>
|
||||
<select type="text" id="node-input-method" style="width:72%;">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
<option value="use">- set by msg.method -</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-url"><i class="fa fa-globe"></i> URL</label>
|
||||
<input type="text" id="node-input-url" placeholder="http://">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-useAuth" style="width: 70%;">Use basic authentication ?</label>
|
||||
</div>
|
||||
<div class="form-row node-input-useAuth-row">
|
||||
<label for="node-input-user"><i class="fa fa-user"></i> Username</label>
|
||||
<input type="text" id="node-input-user">
|
||||
</div>
|
||||
<div class="form-row node-input-useAuth-row">
|
||||
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
|
||||
<input type="password" id="node-input-password">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> Return</label>
|
||||
<select type="text" id="node-input-ret" style="width:72%;">
|
||||
<option value="txt">a UTF-8 string</option>
|
||||
<option value="bin">a binary buffer</option>
|
||||
<option value="obj">a parsed JSON object</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips" id="tip-json" hidden>Tip: If the JSON parse fails the fetched string is returned as-is.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="http request">
|
||||
<p>Provides a node for making http requests.</p>
|
||||
<p>The URL and HTTP method can be configured in the node, if they are left blank they should be set in an incoming message on <code>msg.url</code> and <code>msg.method</code>:</p>
|
||||
<ul>
|
||||
<li><code>url</code>, if set, is used as the url of the request. Must start with http: or https:</li>
|
||||
<li><code>method</code>, if set, is used as the HTTP method of the request.
|
||||
Must be one of <code>GET</code>, <code>PUT</code>, <code>POST</code>, <code>PATCH</code> or <code>DELETE</code> (default: GET)</li>
|
||||
<li><code>headers</code>, if set, should be an object containing field/value
|
||||
pairs to be added as request headers</li>
|
||||
<li><code>payload</code> is sent as the body of the request</li>
|
||||
</ul>
|
||||
<p>When configured within the node, the URL property can contain <a href="http://mustache.github.io/mustache.5.html" target="_new">mustache-style</a> tags. These allow the
|
||||
url to be constructed using values of the incoming message. For example, if the url is set to
|
||||
<code>example.com/{{topic}}</code>, it will have the value of <code>msg.topic</code> automatically inserted.</p>
|
||||
<p>
|
||||
The output message contains the following properties:
|
||||
<ul>
|
||||
<li><code>payload</code> is the body of the response</li>
|
||||
<li><code>statusCode</code> is the status code of the response, or the error code if the request could not be completed</li>
|
||||
<li><code>headers</code> is an object containing the response headers</li>
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('http in',{
|
||||
category: 'input',
|
||||
color:"rgb(231, 231, 174)",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
url: {value:"",required:true},
|
||||
method: {value:"get",required:true}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "white-globe.png",
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
} else if (this.url) {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) != "/") {
|
||||
root = root+"/";
|
||||
}
|
||||
if (this.url.charAt(0) == "/") {
|
||||
root += this.url.slice(1);
|
||||
} else {
|
||||
root += this.url;
|
||||
}
|
||||
return "["+this.method+"] "+root;
|
||||
} else {
|
||||
return "http";
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) == "/") {
|
||||
root = root.slice(0,-1);
|
||||
}
|
||||
if (root == "") {
|
||||
$("#node-input-tip").hide();
|
||||
} else {
|
||||
$("#node-input-path").html(root);
|
||||
$("#node-input-tip").show();
|
||||
}
|
||||
//document.getElementById("node-config-wsdocpath").innerHTML=
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
RED.nodes.registerType('http response',{
|
||||
category: 'output',
|
||||
color:"rgb(231, 231, 174)",
|
||||
defaults: {
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
align: "right",
|
||||
icon: "white-globe.png",
|
||||
label: function() {
|
||||
return this.name||"http";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('http request',{
|
||||
category: 'function',
|
||||
color:"rgb(231, 231, 174)",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
method:{value:"GET"},
|
||||
ret: {value:"txt"},
|
||||
url:{value:""},
|
||||
//user -> credentials
|
||||
//pass -> credentials
|
||||
},
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
align: "right",
|
||||
icon: "white-globe.png",
|
||||
label: function() {
|
||||
return this.name||"http request";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.credentials.user || this.credentials.has_password) {
|
||||
$('#node-input-useAuth').prop('checked', true);
|
||||
$(".node-input-useAuth-row").show();
|
||||
} else {
|
||||
$('#node-input-useAuth').prop('checked', false);
|
||||
$(".node-input-useAuth-row").hide();
|
||||
}
|
||||
|
||||
$("#node-input-useAuth").change(function() {
|
||||
if ($(this).is(":checked")) {
|
||||
$(".node-input-useAuth-row").show();
|
||||
} else {
|
||||
$(".node-input-useAuth-row").hide();
|
||||
$('#node-input-user').val('');
|
||||
$('#node-input-password').val('');
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-ret").change(function() {
|
||||
if ($("#node-input-ret").val() === "obj") {
|
||||
$("#tip-json").show();
|
||||
} else {
|
||||
$("#tip-json").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,298 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var http = require("follow-redirects").http;
|
||||
var https = require("follow-redirects").https;
|
||||
var urllib = require("url");
|
||||
var express = require("express");
|
||||
var getBody = require('raw-body');
|
||||
var mustache = require("mustache");
|
||||
var querystring = require("querystring");
|
||||
var cors = require('cors');
|
||||
var jsonParser = express.json();
|
||||
var urlencParser = express.urlencoded();
|
||||
var onHeaders = require('on-headers');
|
||||
|
||||
function rawBodyParser(req, res, next) {
|
||||
if (req._body) { return next(); }
|
||||
req.body = "";
|
||||
req._body = true;
|
||||
getBody(req, {
|
||||
limit: '1mb',
|
||||
length: req.headers['content-length'],
|
||||
encoding: 'utf8'
|
||||
}, function (err, buf) {
|
||||
if (err) { return next(err); }
|
||||
req.body = buf;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function HTTPIn(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
if (RED.settings.httpNodeRoot !== false) {
|
||||
|
||||
this.url = n.url;
|
||||
this.method = n.method;
|
||||
|
||||
var node = this;
|
||||
|
||||
this.errorHandler = function(err,req,res,next) {
|
||||
node.warn(err);
|
||||
res.send(500);
|
||||
};
|
||||
|
||||
this.callback = function(req,res) {
|
||||
if (node.method == "post") {
|
||||
node.send({req:req,res:res,payload:req.body});
|
||||
} else if (node.method == "get") {
|
||||
node.send({req:req,res:res,payload:req.query});
|
||||
} else {
|
||||
node.send({req:req,res:res});
|
||||
}
|
||||
};
|
||||
|
||||
var corsHandler = function(req,res,next) { next(); }
|
||||
|
||||
if (RED.settings.httpNodeCors) {
|
||||
corsHandler = cors(RED.settings.httpNodeCors);
|
||||
RED.httpNode.options(this.url,corsHandler);
|
||||
}
|
||||
|
||||
var metricsHandler = function(req,res,next) { next(); }
|
||||
|
||||
if (this.metric()) {
|
||||
metricsHandler = function(req, res, next) {
|
||||
var startAt = process.hrtime();
|
||||
onHeaders(res, function() {
|
||||
if(res._msgId) {
|
||||
var diff = process.hrtime(startAt);
|
||||
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
|
||||
var metricResponseTime = ms.toFixed(3);
|
||||
var metricContentLength = res._headers["content-length"];
|
||||
//assuming that _id has been set for res._metrics in HttpOut node!
|
||||
node.metric("response.time.millis", {_id:res._msgId} , metricResponseTime);
|
||||
node.metric("response.content-length.bytes", {_id:res._msgId} , metricContentLength);
|
||||
}
|
||||
});
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
if (this.method == "get") {
|
||||
RED.httpNode.get(this.url,corsHandler,metricsHandler,this.callback,this.errorHandler);
|
||||
} else if (this.method == "post") {
|
||||
RED.httpNode.post(this.url,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
|
||||
} else if (this.method == "put") {
|
||||
RED.httpNode.put(this.url,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
|
||||
} else if (this.method == "delete") {
|
||||
RED.httpNode.delete(this.url,corsHandler,metricsHandler,this.callback,this.errorHandler);
|
||||
}
|
||||
|
||||
this.on("close",function() {
|
||||
var routes = RED.httpNode.routes[this.method];
|
||||
for (var i = 0; i<routes.length; i++) {
|
||||
if (routes[i].path == this.url) {
|
||||
routes.splice(i,1);
|
||||
//break;
|
||||
}
|
||||
}
|
||||
if (RED.settings.httpNodeCors) {
|
||||
var routes = RED.httpNode.routes['options'];
|
||||
if (routes) {
|
||||
for (var j = 0; j<routes.length; j++) {
|
||||
if (routes[j].path == this.url) {
|
||||
routes.splice(j,1);
|
||||
//break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.warn("Cannot create http-in node when httpNodeRoot set to false");
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("http in",HTTPIn);
|
||||
|
||||
|
||||
function HTTPOut(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.on("input",function(msg) {
|
||||
if (msg.res) {
|
||||
if (msg.headers) {
|
||||
msg.res.set(msg.headers);
|
||||
}
|
||||
var statusCode = msg.statusCode || 200;
|
||||
if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
|
||||
msg.res.jsonp(statusCode,msg.payload);
|
||||
} else {
|
||||
if (msg.res.get('content-length') == null) {
|
||||
var len;
|
||||
if (msg.payload == null) {
|
||||
len = 0;
|
||||
} else if (typeof msg.payload == "number") {
|
||||
len = Buffer.byteLength(""+msg.payload);
|
||||
} else {
|
||||
len = Buffer.byteLength(msg.payload);
|
||||
}
|
||||
msg.res.set('content-length', len);
|
||||
}
|
||||
|
||||
msg.res._msgId = msg._id;
|
||||
msg.res.send(statusCode,msg.payload);
|
||||
}
|
||||
} else {
|
||||
node.warn("No response object");
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("http response",HTTPOut);
|
||||
|
||||
|
||||
function HTTPRequest(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var nodeUrl = n.url;
|
||||
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
||||
var nodeMethod = n.method || "GET";
|
||||
this.ret = n.ret || "txt";
|
||||
var node = this;
|
||||
this.on("input",function(msg) {
|
||||
var preRequestTimestamp = process.hrtime();
|
||||
node.status({fill:"blue",shape:"dot",text:"requesting"});
|
||||
var url;
|
||||
if (msg.url) {
|
||||
if (n.url && (n.url !== msg.url)) {
|
||||
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
url = msg.url;
|
||||
} else if (isTemplatedUrl) {
|
||||
url = mustache.render(nodeUrl,msg);
|
||||
} else {
|
||||
url = nodeUrl;
|
||||
}
|
||||
// url must start http:// or https:// so assume http:// if not set
|
||||
if (!((url.indexOf("http://")===0) || (url.indexOf("https://")===0))) {
|
||||
url = "http://"+url;
|
||||
}
|
||||
|
||||
var method;
|
||||
if (msg.method) { // if method set in msg
|
||||
if (n.method && (n.method !== "use")) { // warn if override option not set
|
||||
node.warn("Deprecated: msg properties should not override fixed node properties. Use explicit override option. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
method = msg.method.toUpperCase(); // but use it anyway
|
||||
} else {
|
||||
if (n.method !== "use") {
|
||||
method = nodeMethod.toUpperCase(); // otherwise use the selected method
|
||||
} else { // unless they selected override
|
||||
method = "GET"; // - in which case default to GET
|
||||
}
|
||||
}
|
||||
var opts = urllib.parse(url);
|
||||
opts.method = method;
|
||||
opts.headers = {};
|
||||
if (msg.headers) {
|
||||
for (var v in msg.headers) {
|
||||
if (msg.headers.hasOwnProperty(v)) {
|
||||
var name = v.toLowerCase();
|
||||
if (name !== "content-type" && name !== "content-length") {
|
||||
// only normalise the known headers used later in this
|
||||
// function. Otherwise leave them alone.
|
||||
name = v;
|
||||
}
|
||||
opts.headers[name] = msg.headers[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.credentials && this.credentials.user) {
|
||||
opts.auth = this.credentials.user+":"+(this.credentials.password||"");
|
||||
}
|
||||
var payload = null;
|
||||
|
||||
if (msg.payload && (method == "POST" || method == "PUT" || method == "PATCH" ) ) {
|
||||
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
|
||||
payload = msg.payload;
|
||||
} else if (typeof msg.payload == "number") {
|
||||
payload = msg.payload+"";
|
||||
} else {
|
||||
if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
|
||||
payload = querystring.stringify(msg.payload);
|
||||
} else {
|
||||
payload = JSON.stringify(msg.payload);
|
||||
if (opts.headers['content-type'] == null) {
|
||||
opts.headers['content-type'] = "application/json";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (opts.headers['content-length'] == null) {
|
||||
opts.headers['content-length'] = Buffer.byteLength(payload);
|
||||
}
|
||||
}
|
||||
var req = ((/^https/.test(url))?https:http).request(opts,function(res) {
|
||||
(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
|
||||
msg.statusCode = res.statusCode;
|
||||
msg.headers = res.headers;
|
||||
msg.payload = "";
|
||||
res.on('data',function(chunk) {
|
||||
msg.payload += chunk;
|
||||
});
|
||||
res.on('end',function() {
|
||||
if (node.metric()) {
|
||||
// Calculate request time
|
||||
var diff = process.hrtime(preRequestTimestamp);
|
||||
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
|
||||
var metricRequestDurationMillis = ms.toFixed(3);
|
||||
node.metric("duration.millis", msg, metricRequestDurationMillis);
|
||||
if(res.client && res.client.bytesRead) {
|
||||
node.metric("size.bytes", msg, res.client.bytesRead);
|
||||
}
|
||||
}
|
||||
if (node.ret === "bin") {
|
||||
msg.payload = new Buffer(msg.payload,"binary");
|
||||
}
|
||||
else if (node.ret === "obj") {
|
||||
try { msg.payload = JSON.parse(msg.payload); }
|
||||
catch(e) { node.warn("JSON parse error"); }
|
||||
}
|
||||
node.send(msg);
|
||||
node.status({});
|
||||
});
|
||||
});
|
||||
req.on('error',function(err) {
|
||||
msg.payload = err.toString() + " : " + url;
|
||||
msg.statusCode = err.code;
|
||||
node.send(msg);
|
||||
node.status({fill:"red",shape:"ring",text:err.code});
|
||||
});
|
||||
if (payload) {
|
||||
req.write(payload);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("http request",HTTPRequest,{
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<script type="text/javascript">
|
||||
function ws_oneditprepare() {
|
||||
$("#websocket-client-row").hide();
|
||||
$("#node-input-mode").change(function(){
|
||||
if( $("#node-input-mode").val() === 'client') {
|
||||
$("#websocket-server-row").hide();
|
||||
$("#websocket-client-row").show();
|
||||
}
|
||||
else {
|
||||
$("#websocket-server-row").show();
|
||||
$("#websocket-client-row").hide();
|
||||
}
|
||||
});
|
||||
|
||||
if(this.client) {
|
||||
$("#node-input-mode").val('client').change();
|
||||
}
|
||||
else {
|
||||
$("#node-input-mode").val('server').change();
|
||||
}
|
||||
}
|
||||
|
||||
function ws_oneditsave() {
|
||||
if($("#node-input-mode").val() === 'client') {
|
||||
$("#node-input-server").append('<option value="">Dummy</option>');
|
||||
$("#node-input-server").val('');
|
||||
}
|
||||
else {
|
||||
$("#node-input-client").append('<option value="">Dummy</option>');
|
||||
$("#node-input-client").val('');
|
||||
}
|
||||
}
|
||||
|
||||
function ws_label() {
|
||||
var nodeid = (this.client)?this.client:this.server;
|
||||
var wsNode = RED.nodes.node(nodeid);
|
||||
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
|
||||
}
|
||||
|
||||
function ws_validateserver() {
|
||||
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return RED.nodes.node(this.server) != null;
|
||||
}
|
||||
}
|
||||
|
||||
function ws_validateclient() {
|
||||
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
|
||||
return RED.nodes.node(this.client) != null;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- WebSocket Input Node -->
|
||||
<script type="text/x-red" data-template-name="websocket in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
|
||||
<select id="node-input-mode">
|
||||
<option value="server">Listen on</option>
|
||||
<option value="client">Connect to</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="websocket-server-row">
|
||||
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row" id="websocket-client-row">
|
||||
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
|
||||
<input type="text" id="node-input-client">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="websocket in">
|
||||
<p>WebSocket input node.</p>
|
||||
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
|
||||
The socket can be configured to expect a properly formed JSON string, in which
|
||||
case it will parse the JSON and send on the resulting object as the entire message.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('websocket in',{
|
||||
category: 'input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"websocket-listener", validate: ws_validateserver},
|
||||
client: {type:"websocket-client", validate: ws_validateclient}
|
||||
},
|
||||
color:"rgb(215, 215, 160)",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "white-globe.png",
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
label: ws_label,
|
||||
oneditsave: ws_oneditsave,
|
||||
oneditprepare: ws_oneditprepare
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- WebSocket out Node -->
|
||||
<script type="text/x-red" data-template-name="websocket out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
|
||||
<select id="node-input-mode">
|
||||
<option value="server">Listen on</option>
|
||||
<option value="client">Connect to</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="websocket-server-row">
|
||||
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row" id="websocket-client-row">
|
||||
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
|
||||
<input type="text" id="node-input-client">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="websocket out">
|
||||
<p>WebSocket out node.</p>
|
||||
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
|
||||
can be configured to encode the entire message object as a JSON string and send that
|
||||
over the WebSocket.</p>
|
||||
|
||||
<p>If the message arriving at this node started at a WebSocket In node, the message
|
||||
will be sent back to the client that triggered the flow. Otherwise, the message
|
||||
will be broadcast to all connected clients.</p>
|
||||
<p>If you want to broadcast a message that started at a WebSocket In node, you
|
||||
should delete the <b>msg._session</b> property within the flow</p>.
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('websocket out',{
|
||||
category: 'output',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"websocket-listener", validate: ws_validateserver},
|
||||
client: {type:"websocket-client", validate: ws_validateclient}
|
||||
},
|
||||
color:"rgb(215, 215, 160)",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "white-globe.png",
|
||||
align: "right",
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
label: ws_label,
|
||||
oneditsave: ws_oneditsave,
|
||||
oneditprepare: ws_oneditprepare
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- WebSocket Server configuration node -->
|
||||
<script type="text/x-red" data-template-name="websocket-listener">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> Path</label>
|
||||
<input type="text" id="node-config-input-path" placeholder="/ws/example">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-wholemsg"> </label>
|
||||
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
|
||||
<option value="false">Send/Receive payload</option>
|
||||
<option value="true">Send/Receive entire message</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
|
||||
The listener can be configured to send or receive the entire message object as a JSON formatted string.
|
||||
<p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="websocket-listener">
|
||||
<p>This configuration node creates a WebSocket Server using the specified path</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('websocket-listener',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
|
||||
wholemsg: {value:"false"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
label: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) != "/") {
|
||||
root = root+"/";
|
||||
}
|
||||
if (this.path.charAt(0) == "/") {
|
||||
root += this.path.slice(1);
|
||||
} else {
|
||||
root += this.path;
|
||||
}
|
||||
return root;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) == "/") {
|
||||
root = root.slice(0,-1);
|
||||
}
|
||||
if (root == "") {
|
||||
$("#node-config-ws-tip").hide();
|
||||
} else {
|
||||
$("#node-config-ws-path").html(root);
|
||||
$("#node-config-ws-tip").show();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- WebSocket Client configuration node -->
|
||||
<script type="text/x-red" data-template-name="websocket-client">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> URL</label>
|
||||
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-wholemsg"> </label>
|
||||
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
|
||||
<option value="false">Send/Receive payload</option>
|
||||
<option value="true">Send/Receive entire message</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
<p>URL should use ws:// or wss:// scheme and point to an existing websocket listener.</p>
|
||||
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
|
||||
The client can be configured to send or receive the entire message object as a JSON formatted string.
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="websocket-client">
|
||||
<p>This configuration node connects a WebSocket client to the specified URL.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('websocket-client',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
|
||||
wholemsg: {value:"false"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
label: function() {
|
||||
return this.path;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,204 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var ws = require("ws"),
|
||||
inspect = require("sys").inspect;
|
||||
|
||||
// A node red node that sets up a local websocket server
|
||||
function WebSocketListenerNode(n) {
|
||||
// Create a RED node
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
var node = this;
|
||||
|
||||
// Store local copies of the node configuration (as defined in the .html)
|
||||
node.path = n.path;
|
||||
node.wholemsg = (n.wholemsg === "true");
|
||||
|
||||
node._inputNodes = []; // collection of nodes that want to receive events
|
||||
node._clients = {};
|
||||
|
||||
function handleConnection(/*socket*/socket) {
|
||||
var id = (1+Math.random()*4294967295).toString(16);
|
||||
node._clients[id] = socket;
|
||||
socket.on('close',function() {
|
||||
delete node._clients[id];
|
||||
});
|
||||
socket.on('message',function(data,flags){
|
||||
node.handleEvent(id,socket,'message',data,flags);
|
||||
});
|
||||
socket.on('error', function(err) {
|
||||
node.warn("An error occured on the ws connection: "+inspect(err));
|
||||
});
|
||||
}
|
||||
|
||||
// match absolute url
|
||||
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
|
||||
if(node.isServer)
|
||||
{
|
||||
var path = RED.settings.httpNodeRoot || "/";
|
||||
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
|
||||
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Listen for 'newListener' events from RED.server
|
||||
node._serverListeners = {};
|
||||
|
||||
var storeListener = function(/*String*/event,/*function*/listener){
|
||||
if(event == "error" || event == "upgrade" || event == "listening"){
|
||||
node._serverListeners[event] = listener;
|
||||
}
|
||||
}
|
||||
|
||||
RED.server.addListener('newListener',storeListener);
|
||||
|
||||
// Create a WebSocket Server
|
||||
node.server = new ws.Server({server:RED.server,path:path});
|
||||
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Stop listening for new listener events
|
||||
RED.server.removeListener('newListener',storeListener);
|
||||
|
||||
node.server.on('connection', handleConnection);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Connect to remote endpoint
|
||||
var socket = new ws(node.path);
|
||||
node.server = socket; // keep for closing
|
||||
handleConnection(socket);
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Remove listeners from RED.server
|
||||
var listener = null;
|
||||
for(var event in node._serverListeners) {
|
||||
if (node._serverListeners.hasOwnProperty(event)) {
|
||||
listener = node._serverListeners[event];
|
||||
if(typeof listener === "function"){
|
||||
RED.server.removeListener(event,listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
node._serverListeners = {};
|
||||
node.server.close();
|
||||
node._inputNodes = [];
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
|
||||
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
|
||||
|
||||
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler){
|
||||
this._inputNodes.push(handler);
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
|
||||
var msg;
|
||||
if (this.wholemsg) {
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
}
|
||||
catch(err) {
|
||||
msg = { payload:data };
|
||||
}
|
||||
} else {
|
||||
msg = {
|
||||
payload:data
|
||||
};
|
||||
}
|
||||
msg._session = {type:"websocket",id:id};
|
||||
|
||||
for (var i = 0; i < this._inputNodes.length; i++) {
|
||||
this._inputNodes[i].send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.broadcast = function(data){
|
||||
try {
|
||||
if(this.isServer) {
|
||||
for (var i = 0; i < this.server.clients.length; i++) {
|
||||
this.server.clients[i].send(data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.server.send(data);
|
||||
}
|
||||
}
|
||||
catch(e) { // swallow any errors
|
||||
this.warn("ws:"+i+" : "+e);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.reply = function(id,data) {
|
||||
var session = this._clients[id];
|
||||
if (session) {
|
||||
try {
|
||||
session.send(data);
|
||||
}
|
||||
catch(e) { // swallow any errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function WebSocketInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
var node = this;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (this.serverConfig) {
|
||||
this.serverConfig.registerInputNode(this);
|
||||
} else {
|
||||
this.error("Missing server configuration");
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("websocket in",WebSocketInNode);
|
||||
|
||||
function WebSocketOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (!this.serverConfig) {
|
||||
this.error("Missing server configuration");
|
||||
}
|
||||
this.on("input", function(msg) {
|
||||
var payload;
|
||||
if (this.serverConfig.wholemsg) {
|
||||
delete msg._session;
|
||||
payload = JSON.stringify(msg);
|
||||
} else {
|
||||
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
|
||||
payload = RED.util.ensureString(msg.payload);
|
||||
}
|
||||
else {
|
||||
payload = msg.payload;
|
||||
}
|
||||
}
|
||||
if (msg._session && msg._session.type == "websocket") {
|
||||
node.serverConfig.reply(msg._session.id,payload);
|
||||
} else {
|
||||
node.serverConfig.broadcast(payload,function(error){
|
||||
if (!!error) {
|
||||
node.warn("An error occurred while sending:" + inspect(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket out",WebSocketOutNode);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var Notify = require("fs.notify");
|
||||
var fs = require("fs");
|
||||
var sep = require("path").sep;
|
||||
|
||||
function WatchNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.files = n.files.split(",");
|
||||
for (var f =0; f < this.files.length; f++) {
|
||||
this.files[f] = this.files[f].trim();
|
||||
}
|
||||
this.p = (this.files.length == 1) ? this.files[0] : JSON.stringify(this.files);
|
||||
var node = this;
|
||||
|
||||
var notifications = new Notify(node.files);
|
||||
notifications.on('change', function (file, event, path) {
|
||||
try {
|
||||
if (fs.statSync(path).isDirectory()) { path = path + sep + file; }
|
||||
} catch(e) { }
|
||||
var msg = { payload: path, topic: node.p, file: file };
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
notifications.on('error', function (error, path) {
|
||||
node.warn(error);
|
||||
});
|
||||
|
||||
this.close = function() {
|
||||
notifications.close();
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("watch",WatchNode);
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="serial in">
|
||||
<div class="form-row node-input-serial">
|
||||
<label for="node-input-serial"><i class="fa fa-random"></i> Serial Port</label>
|
||||
<input type="text" id="node-input-serial">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="serial in">
|
||||
<p>Reads data from a local serial port.</p>
|
||||
<p>Can either <ul><li>wait for a "split" character (default \n). Also accepts hex notation (0x0a).</li>
|
||||
<li>Wait for a timeout in milliseconds for the first character received</li>
|
||||
<li>Wait to fill a fixed sized buffer</li></ul></p>
|
||||
<p>It then outputs <b>msg.payload</b> as either a UTF8 ascii string or a binary Buffer object.</p>
|
||||
<p>If no split character is specified, or a timeout or buffer size of 0, then a stream of single characters is sent - again either as ascii chars or size 1 binary buffers.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('serial in',{
|
||||
category: 'input',
|
||||
defaults: {
|
||||
name: {name:""},
|
||||
serial: {type:"serial-port",required:true}
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "serial.png",
|
||||
label: function() {
|
||||
var serialNode = RED.nodes.node(this.serial);
|
||||
return this.name||(serialNode?serialNode.label().split(":")[0]:"serial");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="serial out">
|
||||
<div class="form-row node-input-serial">
|
||||
<label for="node-input-serial"><i class="fa fa-random"></i> Serial Port</label>
|
||||
<input type="text" id="node-input-serial">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="serial out">
|
||||
<p>Provides a connection to an outbound serial port.</p>
|
||||
<p>Only the <b>msg.payload</b> is sent.</p>
|
||||
<p>Optionally the new line character used to split the input can be appended to every message sent out to the serial port.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('serial out',{
|
||||
category: 'output',
|
||||
defaults: {
|
||||
name: {name:""},
|
||||
serial: {type:"serial-port",required:true}
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "serial.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
var serialNode = RED.nodes.node(this.serial);
|
||||
return this.name||(serialNode?serialNode.label().split(":")[0]:"serial");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="serial-port">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-serialport"><i class="fa fa-random"></i> Serial Port</label>
|
||||
<input type="text" id="node-config-input-serialport" style="width:60%;" placeholder="/dev/ttyUSB0"/>
|
||||
<a id="node-config-lookup-serial" class="btn"><i id="node-config-lookup-serial-icon" class="fa fa-search"></i></a>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<table><tr>
|
||||
<td width="102px"><i class="fa fa-wrench"></i> Settings</td>
|
||||
<td width="100px">Baud Rate</td>
|
||||
<td width="80px">Data Bits</td>
|
||||
<td width="80px">Parity</td>
|
||||
<td width="80px">Stop Bits</td>
|
||||
</tr><tr><td> </td>
|
||||
<td>
|
||||
<select type="text" id="node-config-input-serialbaud" style="width: 100px;">
|
||||
<option value="115200">115200</option>
|
||||
<option value="57600">57600</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="19200">19200</option>
|
||||
<option value="9600">9600</option>
|
||||
<option value="4800">4800</option>
|
||||
<option value="2400">2400</option>
|
||||
<option value="1800">1800</option>
|
||||
<option value="1200">1200</option>
|
||||
<option value="600">600</option>
|
||||
<option value="300">300</option>
|
||||
<option value="200">200</option>
|
||||
<option value="150">150</option>
|
||||
<option value="134">134</option>
|
||||
<option value="110">110</option>
|
||||
<option value="75">75</option>
|
||||
<option value="50">50</option>
|
||||
</select>
|
||||
</td><td>
|
||||
<select type="text" id="node-config-input-databits" style="width: 80px;">
|
||||
<option value="8">8</option>
|
||||
<option value="7">7</option>
|
||||
<option value="6">6</option>
|
||||
<option value="5">5</option>
|
||||
</select>
|
||||
</td><td>
|
||||
<select type="text" id="node-config-input-parity" style="width: 80px;">
|
||||
<option value="none">None</option>
|
||||
<option value="even">Even</option>
|
||||
<option value="mark">Mark</option>
|
||||
<option value="odd">Odd</option>
|
||||
<option value="space">Space</option>
|
||||
</select>
|
||||
</td><td>
|
||||
<select type="text" id="node-config-input-stopbits" style="width: 80px;">
|
||||
<option value="2">2</option>
|
||||
<option value="1">1</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr></table><br/>
|
||||
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-sign-in"></i> Input</label>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left: 10px;">
|
||||
Split input
|
||||
<select type="text" id="node-config-input-out" style="margin-left: 5px; width:200px;">
|
||||
<option value="char">on the character</option>
|
||||
<option value="time">after a timeout of</option>
|
||||
<option value="count">into fixed lengths of</option>
|
||||
</select>
|
||||
<input type="text" id="node-config-input-newline" style="width:50px;">
|
||||
<span id="node-units"></span>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left: 10px;">
|
||||
and deliver
|
||||
<select type="text" id="node-config-input-bin" style="margin-left: 5px; width: 150px;">
|
||||
<option value="false">ascii strings</option>
|
||||
<option value="bin">binary buffers</option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div id="node-config-addchar">
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-sign-out"></i> Output</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input style="width: 30px;margin-left: 10px; vertical-align: top;" type="checkbox" id="node-config-input-addchar"><label style="width: auto;" for="node-config-input-addchar">add split character to output messages</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-tips" id="tip-split">Tip: the "Split on" character is used to split the input into separate messages. It can also be added to every message sent out to the serial port.</div>
|
||||
<div class="form-tips" id="tip-bin" hidden>Tip: In timeout mode timeout starts from arrival of first character.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('serial-port',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
//name: {value:""},
|
||||
serialport: {value:"",required:true},
|
||||
serialbaud: {value:57600,required:true},
|
||||
databits: {value:8,required:true},
|
||||
parity: {value:"none",required:true},
|
||||
stopbits: {value:1,required:true},
|
||||
newline: {value:"\\n"},
|
||||
bin: {value:"false"},
|
||||
out: {value:"char"},
|
||||
addchar: {value:false}
|
||||
},
|
||||
label: function() {
|
||||
this.serialbaud = this.serialbaud || 57600;
|
||||
this.databits = this.databits || 8;
|
||||
this.parity = this.parity || 'none';
|
||||
this.stopbits = this.stopbits || 1;
|
||||
return this.serialport+":"+this.serialbaud+"-"+this.databits+this.parity.charAt(0).toUpperCase()+this.stopbits;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var previous = null;
|
||||
$("#node-config-input-out").on('focus', function () { previous = this.value; }).change(function() {
|
||||
if (previous == null) { previous = $("#node-config-input-out").val(); }
|
||||
if ($("#node-config-input-out").val() == "char") {
|
||||
if (previous != "char") { $("#node-config-input-newline").val("\\n"); }
|
||||
$("#node-units").text("");
|
||||
$("#node-config-addchar").show();
|
||||
$("#tip-split").show();
|
||||
$("#tip-bin").hide();
|
||||
}
|
||||
else if ($("#node-config-input-out").val() == "time") {
|
||||
if (previous != "time") { $("#node-config-input-newline").val("0"); }
|
||||
$("#node-units").text("ms");
|
||||
$("#node-config-addchar").hide();
|
||||
$("#node-config-input-addchar").val("false");
|
||||
$("#tip-split").hide();
|
||||
$("#tip-bin").show();
|
||||
}
|
||||
else {
|
||||
if (previous != "count") { $("#node-config-input-newline").val(""); }
|
||||
$("#node-units").text("chars");
|
||||
$("#node-config-addchar").hide();
|
||||
$("#node-config-input-addchar").val("false");
|
||||
$("#tip-split").hide();
|
||||
$("#tip-bin").hide();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
$("#node-config-input-serialport").autocomplete( "destroy" );
|
||||
} catch(err) {
|
||||
}
|
||||
$("#node-config-lookup-serial").click(function() {
|
||||
//$("#node-config-lookup-serial-icon").removeClass('fa fa-search');
|
||||
//$("#node-config-lookup-serial-icon").addClass('fa fa-spinner');
|
||||
$("#node-config-lookup-serial").addClass('disabled');
|
||||
$.getJSON('serialports',function(data) {
|
||||
//$("#node-config-lookup-serial-icon").addClass('fa fa-search');
|
||||
//$("#node-config-lookup-serial-icon").removeClass('fa fa-spinner');
|
||||
$("#node-config-lookup-serial").removeClass('disabled');
|
||||
var ports = [];
|
||||
$.each(data, function(i, port){
|
||||
ports.push(port.comName);
|
||||
});
|
||||
$("#node-config-input-serialport").autocomplete({
|
||||
source:ports,
|
||||
minLength:0,
|
||||
close: function( event, ui ) {
|
||||
$("#node-config-input-serialport").autocomplete( "destroy" );
|
||||
}
|
||||
}).autocomplete("search","");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,307 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var settings = RED.settings;
|
||||
var events = require("events");
|
||||
var util = require("util");
|
||||
var serialp = require("serialport");
|
||||
var bufMaxSize = 32768; // Max serial buffer size, for inputs...
|
||||
|
||||
// TODO: 'serialPool' should be encapsulated in SerialPortNode
|
||||
|
||||
function SerialPortNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.serialport = n.serialport;
|
||||
this.newline = n.newline;
|
||||
this.addchar = n.addchar || "false";
|
||||
this.serialbaud = parseInt(n.serialbaud) || 57600;
|
||||
this.databits = parseInt(n.databits) || 8;
|
||||
this.parity = n.parity || "none";
|
||||
this.stopbits = parseInt(n.stopbits) || 1;
|
||||
this.bin = n.bin || "false";
|
||||
this.out = n.out || "char";
|
||||
}
|
||||
RED.nodes.registerType("serial-port",SerialPortNode);
|
||||
|
||||
function SerialOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.serial = n.serial;
|
||||
this.serialConfig = RED.nodes.getNode(this.serial);
|
||||
|
||||
if (this.serialConfig) {
|
||||
var node = this;
|
||||
node.port = serialPool.get(this.serialConfig.serialport,
|
||||
this.serialConfig.serialbaud,
|
||||
this.serialConfig.databits,
|
||||
this.serialConfig.parity,
|
||||
this.serialConfig.stopbits,
|
||||
this.serialConfig.newline);
|
||||
node.addCh = "";
|
||||
if (node.serialConfig.addchar == "true") {
|
||||
node.addCh = this.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0");
|
||||
}
|
||||
node.on("input",function(msg) {
|
||||
var payload = msg.payload;
|
||||
if (!Buffer.isBuffer(payload)) {
|
||||
if (typeof payload === "object") {
|
||||
payload = JSON.stringify(payload);
|
||||
} else {
|
||||
payload = payload.toString();
|
||||
}
|
||||
payload += node.addCh;
|
||||
} else if (node.addCh !== "") {
|
||||
payload = Buffer.concat([payload,new Buffer(node.addCh)]);
|
||||
}
|
||||
node.port.write(payload,function(err,res) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
node.port.on('ready', function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
node.port.on('closed', function() {
|
||||
node.status({fill:"red",shape:"ring",text:"not connected"});
|
||||
});
|
||||
} else {
|
||||
this.error("missing serial config");
|
||||
}
|
||||
|
||||
this.on("close", function(done) {
|
||||
if (this.serialConfig) {
|
||||
serialPool.close(this.serialConfig.serialport,done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("serial out",SerialOutNode);
|
||||
|
||||
|
||||
function SerialInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.serial = n.serial;
|
||||
this.serialConfig = RED.nodes.getNode(this.serial);
|
||||
|
||||
if (this.serialConfig) {
|
||||
var node = this;
|
||||
node.tout = null;
|
||||
var buf;
|
||||
if (node.serialConfig.out != "count") { buf = new Buffer(bufMaxSize); }
|
||||
else { buf = new Buffer(Number(node.serialConfig.newline)); }
|
||||
var i = 0;
|
||||
node.status({fill:"grey",shape:"dot",text:"unknown"});
|
||||
node.port = serialPool.get(this.serialConfig.serialport,
|
||||
this.serialConfig.serialbaud,
|
||||
this.serialConfig.databits,
|
||||
this.serialConfig.parity,
|
||||
this.serialConfig.stopbits,
|
||||
this.serialConfig.newline
|
||||
);
|
||||
|
||||
var splitc;
|
||||
if (node.serialConfig.newline.substr(0,2) == "0x") {
|
||||
splitc = new Buffer([parseInt(node.serialConfig.newline)]);
|
||||
} else {
|
||||
splitc = new Buffer(node.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0"));
|
||||
}
|
||||
|
||||
this.port.on('data', function(msg) {
|
||||
// single char buffer
|
||||
if ((node.serialConfig.newline === 0)||(node.serialConfig.newline === "")) {
|
||||
if (node.serialConfig.bin !== "bin") { node.send({"payload": String.fromCharCode(msg)}); }
|
||||
else { node.send({"payload": new Buffer([msg])}); }
|
||||
}
|
||||
else {
|
||||
// do the timer thing
|
||||
if (node.serialConfig.out === "time") {
|
||||
if (node.tout) {
|
||||
i += 1;
|
||||
buf[i] = msg;
|
||||
}
|
||||
else {
|
||||
node.tout = setTimeout(function () {
|
||||
node.tout = null;
|
||||
var m = new Buffer(i+1);
|
||||
buf.copy(m,0,0,i+1);
|
||||
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
|
||||
node.send({"payload": m});
|
||||
m = null;
|
||||
}, node.serialConfig.newline);
|
||||
i = 0;
|
||||
buf[0] = msg;
|
||||
}
|
||||
}
|
||||
// count bytes into a buffer...
|
||||
else if (node.serialConfig.out === "count") {
|
||||
buf[i] = msg;
|
||||
i += 1;
|
||||
if ( i >= parseInt(node.serialConfig.newline)) {
|
||||
var m = new Buffer(i);
|
||||
buf.copy(m,0,0,i);
|
||||
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
|
||||
node.send({"payload":m});
|
||||
m = null;
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
// look to match char...
|
||||
else if (node.serialConfig.out === "char") {
|
||||
buf[i] = msg;
|
||||
i += 1;
|
||||
if ((msg === splitc[0]) || (i === bufMaxSize)) {
|
||||
var m = new Buffer(i);
|
||||
buf.copy(m,0,0,i);
|
||||
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
|
||||
node.send({"payload":m});
|
||||
m = null;
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
else { console.log("Should never get here"); }
|
||||
}
|
||||
});
|
||||
this.port.on('ready', function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
this.port.on('closed', function() {
|
||||
node.status({fill:"red",shape:"ring",text:"not connected"});
|
||||
});
|
||||
} else {
|
||||
this.error("missing serial config");
|
||||
}
|
||||
|
||||
this.on("close", function(done) {
|
||||
if (this.serialConfig) {
|
||||
serialPool.close(this.serialConfig.serialport,done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("serial in",SerialInNode);
|
||||
|
||||
|
||||
var serialPool = function() {
|
||||
var connections = {};
|
||||
return {
|
||||
get:function(port,baud,databits,parity,stopbits,newline,callback) {
|
||||
var id = port;
|
||||
if (!connections[id]) {
|
||||
connections[id] = function() {
|
||||
var obj = {
|
||||
_emitter: new events.EventEmitter(),
|
||||
serial: null,
|
||||
_closing: false,
|
||||
tout: null,
|
||||
on: function(a,b) { this._emitter.on(a,b); },
|
||||
close: function(cb) { this.serial.close(cb); },
|
||||
write: function(m,cb) { this.serial.write(m,cb); },
|
||||
}
|
||||
//newline = newline.replace("\\n","\n").replace("\\r","\r");
|
||||
var setupSerial = function() {
|
||||
//if (newline == "") {
|
||||
obj.serial = new serialp.SerialPort(port,{
|
||||
baudrate: baud,
|
||||
databits: databits,
|
||||
parity: parity,
|
||||
stopbits: stopbits,
|
||||
parser: serialp.parsers.raw
|
||||
},true, function(err, results) { if (err) { obj.serial.emit('error',err); } });
|
||||
//}
|
||||
//else {
|
||||
// obj.serial = new serialp.SerialPort(port,{
|
||||
// baudrate: baud,
|
||||
// databits: databits,
|
||||
// parity: parity,
|
||||
// stopbits: stopbits,
|
||||
// parser: serialp.parsers.readline(newline)
|
||||
// },true, function(err, results) { if (err) obj.serial.emit('error',err); });
|
||||
//}
|
||||
obj.serial.on('error', function(err) {
|
||||
util.log("[serial] serial port "+port+" error "+err);
|
||||
obj._emitter.emit('closed');
|
||||
obj.tout = setTimeout(function() {
|
||||
setupSerial();
|
||||
}, settings.serialReconnectTime);
|
||||
});
|
||||
obj.serial.on('close', function() {
|
||||
if (!obj._closing) {
|
||||
util.log("[serial] serial port "+port+" closed unexpectedly");
|
||||
obj._emitter.emit('closed');
|
||||
obj.tout = setTimeout(function() {
|
||||
setupSerial();
|
||||
}, settings.serialReconnectTime);
|
||||
}
|
||||
});
|
||||
obj.serial.on('open',function() {
|
||||
util.log("[serial] serial port "+port+" opened at "+baud+" baud "+databits+""+parity.charAt(0).toUpperCase()+stopbits);
|
||||
if (obj.tout) { clearTimeout(obj.tout); }
|
||||
//obj.serial.flush();
|
||||
obj._emitter.emit('ready');
|
||||
});
|
||||
obj.serial.on('data',function(d) {
|
||||
//console.log(Buffer.isBuffer(d),d.length,d);
|
||||
//if (typeof d !== "string") {
|
||||
// //d = d.toString();
|
||||
for (var z=0; z<d.length; z++) {
|
||||
obj._emitter.emit('data',d[z]);
|
||||
}
|
||||
//}
|
||||
//else {
|
||||
// obj._emitter.emit('data',d);
|
||||
//}
|
||||
});
|
||||
obj.serial.on("disconnect",function() {
|
||||
util.log("[serial] serial port "+port+" gone away");
|
||||
});
|
||||
}
|
||||
setupSerial();
|
||||
return obj;
|
||||
}();
|
||||
}
|
||||
return connections[id];
|
||||
},
|
||||
close: function(port,done) {
|
||||
if (connections[port]) {
|
||||
if (connections[port].tout != null) {
|
||||
clearTimeout(connections[port].tout);
|
||||
}
|
||||
connections[port]._closing = true;
|
||||
try {
|
||||
connections[port].close(function() {
|
||||
util.log("[serial] serial port closed");
|
||||
done();
|
||||
});
|
||||
}
|
||||
catch(err) { }
|
||||
delete connections[port];
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
RED.httpAdmin.get("/serialports", RED.auth.needsPermission('serial.read'), function(req,res) {
|
||||
serialp.list(function (err, ports) {
|
||||
res.json(ports);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,506 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var reconnectTime = RED.settings.socketReconnectTime||10000;
|
||||
var socketTimeout = RED.settings.socketTimeout||null;
|
||||
var net = require('net');
|
||||
|
||||
var connectionPool = {};
|
||||
|
||||
function TcpIn(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.host = n.host;
|
||||
this.port = n.port * 1;
|
||||
this.topic = n.topic;
|
||||
this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
|
||||
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
|
||||
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r");
|
||||
this.base64 = n.base64;
|
||||
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
|
||||
this.closing = false;
|
||||
this.connected = false;
|
||||
var node = this;
|
||||
var count = 0;
|
||||
|
||||
if (!node.server) {
|
||||
var buffer = null;
|
||||
var client;
|
||||
var reconnectTimeout;
|
||||
var end = false;
|
||||
var setupTcpClient = function() {
|
||||
node.log("connecting to "+node.host+":"+node.port);
|
||||
node.status({fill:"grey",shape:"dot",text:"connecting"});
|
||||
var id = (1+Math.random()*4294967295).toString(16);
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
buffer = (node.datatype == 'buffer')? new Buffer(0):"";
|
||||
node.connected = true;
|
||||
node.log("connected to "+node.host+":"+node.port);
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
connectionPool[id] = client;
|
||||
|
||||
client.on('data', function (data) {
|
||||
if (node.datatype != 'buffer') {
|
||||
data = data.toString(node.datatype);
|
||||
}
|
||||
if (node.stream) {
|
||||
if ((node.datatype) === "utf8" && node.newline != "") {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0;i<parts.length-1;i+=1) {
|
||||
var msg = {topic:node.topic, payload:parts[i]};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = parts[parts.length-1];
|
||||
} else {
|
||||
var msg = {topic:node.topic, payload:data};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
} else {
|
||||
if ((typeof data) === "string") {
|
||||
buffer = buffer+data;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
|
||||
}
|
||||
}
|
||||
});
|
||||
client.on('end', function() {
|
||||
if (!node.stream || (node.datatype == "utf8" && node.newline != "" && buffer.length > 0)) {
|
||||
var msg = {topic:node.topic,payload:buffer};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
if (buffer.length !== 0) {
|
||||
end = true; // only ask for fast re-connect if we actually got something
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = null;
|
||||
}
|
||||
});
|
||||
client.on('close', function() {
|
||||
delete connectionPool[id];
|
||||
node.connected = false;
|
||||
node.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
if (!node.closing) {
|
||||
if (end) { // if we were asked to close then try to reconnect once very quick.
|
||||
end = false;
|
||||
reconnectTimeout = setTimeout(setupTcpClient, 20);
|
||||
}
|
||||
else {
|
||||
node.log("connection lost to "+node.host+":"+node.port);
|
||||
reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
|
||||
}
|
||||
} else {
|
||||
if (node.done) { node.done(); }
|
||||
}
|
||||
});
|
||||
client.on('error', function(err) {
|
||||
node.log(err);
|
||||
});
|
||||
}
|
||||
setupTcpClient();
|
||||
|
||||
this.on('close', function(done) {
|
||||
node.done = done;
|
||||
this.closing = true;
|
||||
client.end();
|
||||
clearTimeout(reconnectTimeout);
|
||||
if (!node.connected) { done(); }
|
||||
});
|
||||
} else {
|
||||
var server = net.createServer(function (socket) {
|
||||
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
|
||||
var id = (1+Math.random()*4294967295).toString(16);
|
||||
connectionPool[id] = socket;
|
||||
node.status({text:++count+" connections"});
|
||||
|
||||
var buffer = (node.datatype == 'buffer')? new Buffer(0):"";
|
||||
socket.on('data', function (data) {
|
||||
if (node.datatype != 'buffer') {
|
||||
data = data.toString(node.datatype);
|
||||
}
|
||||
if (node.stream) {
|
||||
if ((typeof data) === "string" && node.newline != "") {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0;i<parts.length-1;i+=1) {
|
||||
var msg = {topic:node.topic, payload:parts[i],ip:socket.remoteAddress,port:socket.remotePort};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = parts[parts.length-1];
|
||||
} else {
|
||||
var msg = {topic:node.topic, payload:data};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
} else {
|
||||
if ((typeof data) === "string") {
|
||||
buffer = buffer+data;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.on('end', function() {
|
||||
if (!node.stream || (node.datatype === "utf8" && node.newline !== "")) {
|
||||
if (buffer.length > 0) {
|
||||
var msg = {topic:node.topic,payload:buffer};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = null;
|
||||
}
|
||||
});
|
||||
socket.on('timeout', function() {
|
||||
node.log('timeout closed socket port '+node.port);
|
||||
socket.end();
|
||||
});
|
||||
socket.on('close', function() {
|
||||
delete connectionPool[id];
|
||||
node.status({text:--count+" connections"});
|
||||
});
|
||||
socket.on('error',function(err) {
|
||||
node.log(err);
|
||||
});
|
||||
});
|
||||
server.on('error', function(err) {
|
||||
if (err) {
|
||||
node.error('unable to listen on port '+node.port+' : '+err);
|
||||
}
|
||||
});
|
||||
server.listen(node.port, function(err) {
|
||||
if (err) {
|
||||
node.error('unable to listen on port '+node.port+' : '+err);
|
||||
} else {
|
||||
node.log('listening on port '+node.port);
|
||||
node.on('close', function() {
|
||||
for (var c in connectionPool) {
|
||||
connectionPool[c].end();
|
||||
connectionPool[c].unref();
|
||||
}
|
||||
node.closing = true;
|
||||
server.close();
|
||||
node.log('stopped listening on port '+node.port);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
RED.nodes.registerType("tcp in",TcpIn);
|
||||
|
||||
function TcpOut(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.host = n.host;
|
||||
this.port = n.port * 1;
|
||||
this.base64 = n.base64;
|
||||
this.doend = n.end || false;
|
||||
this.beserver = n.beserver;
|
||||
this.name = n.name;
|
||||
this.closing = false;
|
||||
this.connected = false;
|
||||
var node = this;
|
||||
|
||||
if (!node.beserver||node.beserver=="client") {
|
||||
var reconnectTimeout;
|
||||
var client = null;
|
||||
var end = false;
|
||||
|
||||
var setupTcpClient = function() {
|
||||
node.log("connecting to "+node.host+":"+node.port);
|
||||
node.status({fill:"grey",shape:"dot",text:"connecting"});
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
node.connected = true;
|
||||
node.log("connected to "+node.host+":"+node.port);
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
client.on('error', function (err) {
|
||||
node.log('error : '+err);
|
||||
});
|
||||
client.on('end', function (err) {
|
||||
});
|
||||
client.on('close', function() {
|
||||
node.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
node.connected = false;
|
||||
client.destroy();
|
||||
if (!node.closing) {
|
||||
if (end) {
|
||||
end = false;
|
||||
reconnectTimeout = setTimeout(setupTcpClient,20);
|
||||
}
|
||||
else {
|
||||
node.log("connection lost to "+node.host+":"+node.port);
|
||||
reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
|
||||
}
|
||||
} else {
|
||||
if (node.done) { node.done(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
setupTcpClient();
|
||||
|
||||
node.on("input", function(msg) {
|
||||
if (node.connected && msg.payload != null) {
|
||||
if (Buffer.isBuffer(msg.payload)) {
|
||||
client.write(msg.payload);
|
||||
} else if (typeof msg.payload === "string" && node.base64) {
|
||||
client.write(new Buffer(msg.payload,'base64'));
|
||||
} else {
|
||||
client.write(new Buffer(""+msg.payload));
|
||||
}
|
||||
if (node.doend === true) {
|
||||
end = true;
|
||||
client.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.done = done;
|
||||
this.closing = true;
|
||||
client.end();
|
||||
clearTimeout(reconnectTimeout);
|
||||
if (!node.connected) { done(); }
|
||||
});
|
||||
|
||||
} else if (node.beserver == "reply") {
|
||||
node.on("input",function(msg) {
|
||||
if (msg._session && msg._session.type == "tcp") {
|
||||
var client = connectionPool[msg._session.id];
|
||||
if (client) {
|
||||
if (Buffer.isBuffer(msg.payload)) {
|
||||
client.write(msg.payload);
|
||||
} else if (typeof msg.payload === "string" && node.base64) {
|
||||
client.write(new Buffer(msg.payload,'base64'));
|
||||
} else {
|
||||
client.write(new Buffer(""+msg.payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var connectedSockets = [];
|
||||
node.status({text:"0 connections"});
|
||||
var server = net.createServer(function (socket) {
|
||||
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
|
||||
var remoteDetails = socket.remoteAddress+":"+socket.remotePort;
|
||||
node.log("connection from "+remoteDetails);
|
||||
connectedSockets.push(socket);
|
||||
node.status({text:connectedSockets.length+" connections"});
|
||||
socket.on('timeout', function() {
|
||||
node.log('timeout closed socket port '+node.port);
|
||||
socket.end();
|
||||
});
|
||||
socket.on('close',function() {
|
||||
node.log("connection closed from "+remoteDetails);
|
||||
connectedSockets.splice(connectedSockets.indexOf(socket),1);
|
||||
node.status({text:connectedSockets.length+" connections"});
|
||||
});
|
||||
socket.on('error',function() {
|
||||
node.log("socket error from "+remoteDetails);
|
||||
connectedSockets.splice(connectedSockets.indexOf(socket),1);
|
||||
node.status({text:connectedSockets.length+" connections"});
|
||||
});
|
||||
});
|
||||
|
||||
node.on("input", function(msg) {
|
||||
if (msg.payload != null) {
|
||||
var buffer;
|
||||
if (Buffer.isBuffer(msg.payload)) {
|
||||
buffer = msg.payload;
|
||||
} else if (typeof msg.payload === "string" && node.base64) {
|
||||
buffer = new Buffer(msg.payload,'base64');
|
||||
} else {
|
||||
buffer = new Buffer(""+msg.payload);
|
||||
}
|
||||
for (var i = 0; i<connectedSockets.length;i+=1) {
|
||||
if (node.doend === true) { connectedSockets[i].end(buffer); }
|
||||
else { connectedSockets[i].write(buffer); }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.on('error', function(err) {
|
||||
if (err) {
|
||||
node.error('unable to listen on port '+node.port+' : '+err);
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(node.port, function(err) {
|
||||
if (err) {
|
||||
node.error('unable to listen on port '+node.port+' : '+err);
|
||||
} else {
|
||||
node.log('listening on port '+node.port);
|
||||
node.on('close', function() {
|
||||
for (var c in connectedSockets) {
|
||||
connectedSockets[c].end();
|
||||
connectedSockets[c].unref();
|
||||
}
|
||||
server.close();
|
||||
node.log('stopped listening on port '+node.port);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("tcp out",TcpOut);
|
||||
|
||||
function TcpGet(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = n.server;
|
||||
this.port = Number(n.port);
|
||||
this.out = n.out;
|
||||
this.splitc = n.splitc;
|
||||
|
||||
if (this.out != "char") { this.splitc = Number(this.splitc); }
|
||||
else { this.splitc.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0"); }
|
||||
|
||||
var buf;
|
||||
if (this.out == "count") { buf = new Buffer(this.splitc); }
|
||||
else { buf = new Buffer(32768); } // set it to 32k... hopefully big enough for most.... but only hopefully
|
||||
|
||||
this.connected = false;
|
||||
var node = this;
|
||||
var client;
|
||||
|
||||
this.on("input", function(msg) {
|
||||
var i = 0;
|
||||
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
|
||||
msg.payload = msg.payload.toString();
|
||||
}
|
||||
if (!node.connected) {
|
||||
client = net.Socket();
|
||||
client.setTimeout(socketTimeout);
|
||||
node.status({});
|
||||
var host = node.server || msg.host;
|
||||
var port = node.port || msg.port;
|
||||
if (host && port) {
|
||||
client.connect(port, host, function() {
|
||||
//node.log('client connected');
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
node.connected = true;
|
||||
client.write(msg.payload);
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.warn("Host and/or port not set");
|
||||
}
|
||||
|
||||
client.on('data', function(data) {
|
||||
//node.log("data:"+ data.length+":"+ data);
|
||||
if (node.splitc === 0) {
|
||||
node.send({"payload": data});
|
||||
}
|
||||
else if (node.out === "sit") { // if we are staying connected just send the buffer
|
||||
node.send({"payload": data});
|
||||
}
|
||||
else {
|
||||
for (var j = 0; j < data.length; j++ ) {
|
||||
if (node.out === "time") {
|
||||
// do the timer thing
|
||||
if (node.tout) {
|
||||
i += 1;
|
||||
buf[i] = data[j];
|
||||
}
|
||||
else {
|
||||
node.tout = setTimeout(function () {
|
||||
node.tout = null;
|
||||
var m = new Buffer(i+1);
|
||||
buf.copy(m,0,0,i+1);
|
||||
node.send({"payload": m});
|
||||
client.end();
|
||||
m = null;
|
||||
}, node.splitc);
|
||||
i = 0;
|
||||
buf[0] = data[j];
|
||||
}
|
||||
}
|
||||
// count bytes into a buffer...
|
||||
else if (node.out == "count") {
|
||||
buf[i] = data[j];
|
||||
i += 1;
|
||||
if ( i >= node.serialConfig.count) {
|
||||
node.send({"payload": buf});
|
||||
client.end();
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
// look for a char
|
||||
else {
|
||||
buf[i] = data[j];
|
||||
i += 1;
|
||||
if (data[j] == node.splitc) {
|
||||
var m = new Buffer(i);
|
||||
buf.copy(m,0,0,i);
|
||||
node.send({"payload": m});
|
||||
client.end();
|
||||
m = null;
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.on('end', function() {
|
||||
//node.log('client disconnected');
|
||||
node.connected = false;
|
||||
node.status({});
|
||||
client = null;
|
||||
});
|
||||
|
||||
client.on('close', function() {
|
||||
if (node.done) { node.done(); }
|
||||
});
|
||||
|
||||
client.on('error', function() {
|
||||
node.log('connect failed');
|
||||
node.status({fill:"red",shape:"ring",text:"error"});
|
||||
if (client) { client.end(); }
|
||||
});
|
||||
|
||||
client.on('timeout',function() {
|
||||
node.log('connect timeout');
|
||||
if (client) {
|
||||
client.end();
|
||||
setTimeout(function() {
|
||||
client.connect(port, host, function() {
|
||||
//node.log('client connected');
|
||||
node.connected = true;
|
||||
client.write(msg.payload);
|
||||
});
|
||||
},reconnectTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
else { client.write(msg.payload); }
|
||||
});
|
||||
|
||||
this.on("close", function(done) {
|
||||
node.done = done;
|
||||
if (client) {
|
||||
buf = null;
|
||||
client.end();
|
||||
}
|
||||
if (!node.connected) { done(); }
|
||||
});
|
||||
|
||||
}
|
||||
RED.nodes.registerType("tcp request",TcpGet);
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- The Input Node -->
|
||||
<script type="text/x-red" data-template-name="udp in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-sign-in"></i> Listen</label>
|
||||
on port <input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
|
||||
for <select id="node-input-multicast" style='width:40%'>
|
||||
<option value="false">udp messages</option>
|
||||
<option value="true">multicast messages</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-group">
|
||||
<label for="node-input-group"><i class="fa fa-list"></i> Group</label>
|
||||
<input type="text" id="node-input-group" placeholder="225.0.18.83">
|
||||
</div>
|
||||
<div class="form-row node-input-iface">
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
|
||||
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> Output</label>
|
||||
<select id="node-input-datatype" style="width: 70%;">
|
||||
<option value="buffer">a Buffer</option>
|
||||
<option value="utf8">a String</option>
|
||||
<option value="base64">a Base64 encoded string</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: Make sure your firewall will allow the data in.</div>
|
||||
<script>
|
||||
$("#node-input-multicast").change(function() {
|
||||
var id = $("#node-input-multicast option:selected").val();
|
||||
if (id == "false") {
|
||||
$(".node-input-group").hide();
|
||||
$(".node-input-iface").hide();
|
||||
}
|
||||
else {
|
||||
$(".node-input-group").show();
|
||||
$(".node-input-iface").show();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="udp in">
|
||||
<p>A udp input node, that produces a <b>msg.payload</b> containing a <i>BUFFER</i>, string, or base64 encoded string. Supports multicast.</p>
|
||||
<p>It also provides <b>msg.ip</b> and <b>msg.port</b> to the ip address and port from which the message was received.</p>
|
||||
<p>On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('udp in',{
|
||||
category: 'input',
|
||||
color:"Silver",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
iface: {value:""},
|
||||
port: {value:"",required:true,validate:RED.validators.number()},
|
||||
datatype: {value:"buffer",required:true},
|
||||
multicast: {value:"false"},
|
||||
group: {value:"",validate:function(v) { return (this.multicast !== "true")||v.length > 0;} }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "bridge-dash.png",
|
||||
label: function() {
|
||||
if (this.multicast=="false") {
|
||||
return this.name||"udp "+this.port;
|
||||
}
|
||||
else return this.name||"udp "+(this.group+":"+this.port);
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- The Output Node -->
|
||||
<script type="text/x-red" data-template-name="udp out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-envelope"></i> Send a</label>
|
||||
<select id="node-input-multicast" style='width:40%'>
|
||||
<option value="false">udp message</option>
|
||||
<option value="broad">broadcast message</option>
|
||||
<option value="multi">multicast message</option>
|
||||
</select>
|
||||
to port <input type="text" id="node-input-port" placeholder="port" style="width: 70px">
|
||||
</div>
|
||||
<div class="form-row node-input-addr">
|
||||
<label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> Address</label>
|
||||
<input type="text" id="node-input-addr" placeholder="destination ip" style="width: 70%;">
|
||||
</div>
|
||||
<div class="form-row node-input-iface">
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
|
||||
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-outport-type"> </label>
|
||||
<select id="node-input-outport-type">
|
||||
<option id="node-input-outport-type-random" value="random">use random local port</option>
|
||||
<option value="fixed">bind to local port</option>
|
||||
</select>
|
||||
<input type="text" id="node-input-outport" style="width: 70px;" placeholder="port">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-base64" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-base64" style="width: 70%;">Decode Base64 encoded payload ?</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>.</div>
|
||||
<script>
|
||||
$("#node-input-multicast").change(function() {
|
||||
var id = $("#node-input-multicast option:selected").val();
|
||||
if (id !== "multi") {
|
||||
$(".node-input-iface").hide();
|
||||
$("#node-input-addr-label").html('<i class="fa fa-list"></i> Address');
|
||||
$("#node-input-addr")[0].placeholder = 'destination ip';
|
||||
}
|
||||
else {
|
||||
$(".node-input-iface").show();
|
||||
$("#node-input-addr-label").html('<i class="fa fa-list"></i> Group');
|
||||
$("#node-input-addr")[0].placeholder = '225.0.18.83';
|
||||
}
|
||||
if (id === "broad") {
|
||||
$("#node-input-addr")[0].placeholder = '255.255.255.255';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="udp out">
|
||||
<p>This node sends <b>msg.payload</b> to the designated udp host and port. Supports multicast.</p>
|
||||
<p>You may also use <b>msg.ip</b> and <b>msg.port</b> to set the destination values.<br/><b>Note</b>: the statically configured values have precedence.</p>
|
||||
<p>If you select broadcast either set the address to the local broadcast ip address, or maybe try 255.255.255.255, which is the global broadcast address.</p>
|
||||
<p>On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('udp out',{
|
||||
category: 'output',
|
||||
color:"Silver",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
addr: {value:""},
|
||||
iface: {value:""},
|
||||
port: {value:""},
|
||||
outport: {value:""},
|
||||
base64: {value:false,required:true},
|
||||
multicast: {value:"false"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "bridge-dash.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||"udp "+(this.addr+":"+this.port);
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var type = this.outport==""?"random":"fixed";
|
||||
$("#node-input-outport-type option").filter(function() {
|
||||
return $(this).val() == type;
|
||||
}).attr('selected',true);
|
||||
|
||||
$("#node-input-outport-type").change(function() {
|
||||
var type = $(this).children("option:selected").val();
|
||||
if (type == "random") {
|
||||
$("#node-input-outport").val("").hide();
|
||||
} else {
|
||||
$("#node-input-outport").show();
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-outport-type").change();
|
||||
|
||||
$("#node-input-multicast").change(function() {
|
||||
var type = $(this).children("option:selected").val();
|
||||
if (type == "false") {
|
||||
$("#node-input-outport-type-random").html("bind to random local port");
|
||||
} else {
|
||||
$("#node-input-outport-type-random").html("bind to target port");
|
||||
}
|
||||
});
|
||||
$("#node-input-multicast").change();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,171 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var dgram = require('dgram');
|
||||
|
||||
// The Input Node
|
||||
function UDPin(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.group = n.group;
|
||||
this.port = n.port;
|
||||
this.datatype = n.datatype;
|
||||
this.iface = n.iface || null;
|
||||
this.multicast = n.multicast;
|
||||
var node = this;
|
||||
|
||||
var server = dgram.createSocket('udp4');
|
||||
|
||||
server.on("error", function (err) {
|
||||
if ((err.code == "EACCES") && (node.port < 1024)) {
|
||||
node.error("UDP access error, you may need root access for ports below 1024");
|
||||
} else {
|
||||
node.error("UDP error : "+err.code);
|
||||
}
|
||||
server.close();
|
||||
});
|
||||
|
||||
server.on('message', function (message, remote) {
|
||||
var msg;
|
||||
if (node.datatype =="base64") {
|
||||
msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port };
|
||||
} else if (node.datatype =="utf8") {
|
||||
msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port };
|
||||
} else {
|
||||
msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
server.on('listening', function () {
|
||||
var address = server.address();
|
||||
node.log('udp listener at ' + address.address + ":" + address.port);
|
||||
if (node.multicast == "true") {
|
||||
server.setBroadcast(true);
|
||||
try {
|
||||
server.setMulticastTTL(128);
|
||||
server.addMembership(node.group,node.iface);
|
||||
node.log("udp multicast group "+node.group);
|
||||
} catch (e) {
|
||||
if (e.errno == "EINVAL") {
|
||||
node.error("Bad Multicast Address");
|
||||
} else if (e.errno == "ENODEV") {
|
||||
node.error("Must be ip address of the required interface");
|
||||
} else {
|
||||
node.error("Error :"+e.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
try {
|
||||
server.close();
|
||||
node.log('udp listener stopped');
|
||||
} catch (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
server.bind(node.port,node.iface);
|
||||
}
|
||||
RED.nodes.registerType("udp in",UDPin);
|
||||
|
||||
|
||||
// The Output Node
|
||||
function UDPout(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
//this.group = n.group;
|
||||
this.port = n.port;
|
||||
this.outport = n.outport||"";
|
||||
this.base64 = n.base64;
|
||||
this.addr = n.addr;
|
||||
this.iface = n.iface || null;
|
||||
this.multicast = n.multicast;
|
||||
var node = this;
|
||||
|
||||
var sock = dgram.createSocket('udp4'); // only use ipv4 for now
|
||||
|
||||
if (node.multicast != "false") {
|
||||
if (node.outport == "") { node.outport = node.port; }
|
||||
sock.bind(node.outport, function() { // have to bind before you can enable broadcast...
|
||||
sock.setBroadcast(true); // turn on broadcast
|
||||
if (node.multicast == "multi") {
|
||||
try {
|
||||
sock.setMulticastTTL(128);
|
||||
sock.addMembership(node.addr,node.iface); // Add to the multicast group
|
||||
node.log('udp multicast ready : '+node.outport+' -> '+node.addr+":"+node.port);
|
||||
} catch (e) {
|
||||
if (e.errno == "EINVAL") {
|
||||
node.error("Bad Multicast Address");
|
||||
} else if (e.errno == "ENODEV") {
|
||||
node.error("Must be ip address of the required interface");
|
||||
} else {
|
||||
node.error("Error :"+e.errno);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.log('udp broadcast ready : '+node.outport+' -> '+node.addr+":"+node.port);
|
||||
}
|
||||
});
|
||||
} else if (node.outport != "") {
|
||||
sock.bind(node.outport);
|
||||
node.log('udp ready : '+node.outport+' -> '+node.addr+":"+node.port);
|
||||
} else {
|
||||
node.log('udp ready : '+node.addr+":"+node.port);
|
||||
}
|
||||
|
||||
node.on("input", function(msg) {
|
||||
if (msg.payload != null) {
|
||||
var add = node.addr || msg.ip || "";
|
||||
var por = node.port || msg.port || 0;
|
||||
if (add == "") {
|
||||
node.warn("udp: ip address not set");
|
||||
} else if (por == 0) {
|
||||
node.warn("udp: port not set");
|
||||
} else if (isNaN(por) || (por < 1) || (por > 65535)) {
|
||||
node.warn("udp: port number not valid");
|
||||
} else {
|
||||
var message;
|
||||
if (node.base64) {
|
||||
message = new Buffer(msg.payload, 'base64');
|
||||
} else if (msg.payload instanceof Buffer) {
|
||||
message = msg.payload;
|
||||
} else {
|
||||
message = new Buffer(""+msg.payload);
|
||||
}
|
||||
sock.send(message, 0, message.length, por, add, function(err, bytes) {
|
||||
if (err) {
|
||||
node.error("udp : "+err);
|
||||
}
|
||||
message = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
try {
|
||||
sock.close();
|
||||
node.log('udp output stopped');
|
||||
} catch (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("udp out",UDPout);
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var util = require("util");
|
||||
var mqtt = require("./mqtt");
|
||||
var settings = require(process.env.NODE_RED_HOME+"/red/red").settings;
|
||||
|
||||
var connections = {};
|
||||
|
||||
function matchTopic(ts,t) {
|
||||
if (ts == "#") {
|
||||
return true;
|
||||
}
|
||||
var re = new RegExp("^"+ts.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||
return re.test(t);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get: function(broker,port,clientid,username,password,will) {
|
||||
var id = "["+(username||"")+":"+(password||"")+"]["+(clientid||"")+"]@"+broker+":"+port;
|
||||
if (!connections[id]) {
|
||||
connections[id] = function() {
|
||||
var uid = (1+Math.random()*4294967295).toString(16);
|
||||
var client = mqtt.createClient(port,broker);
|
||||
client.uid = uid;
|
||||
client.setMaxListeners(0);
|
||||
var options = {keepalive:15};
|
||||
options.clientId = clientid || 'mqtt_' + (1+Math.random()*4294967295).toString(16);
|
||||
options.username = username;
|
||||
options.password = password;
|
||||
options.will = will;
|
||||
var queue = [];
|
||||
var subscriptions = [];
|
||||
var connecting = false;
|
||||
var obj = {
|
||||
_instances: 0,
|
||||
publish: function(msg) {
|
||||
if (client.isConnected()) {
|
||||
client.publish(msg.topic,msg.payload,msg.qos,msg.retain);
|
||||
} else {
|
||||
if (!connecting) {
|
||||
connecting = true;
|
||||
client.connect(options);
|
||||
}
|
||||
queue.push(msg);
|
||||
}
|
||||
},
|
||||
subscribe: function(topic,qos,callback) {
|
||||
subscriptions.push({topic:topic,qos:qos,callback:callback});
|
||||
client.on('message',function(mtopic,mpayload,mqos,mretain) {
|
||||
if (matchTopic(topic,mtopic)) {
|
||||
callback(mtopic,mpayload,mqos,mretain);
|
||||
}
|
||||
});
|
||||
if (client.isConnected()) {
|
||||
client.subscribe(topic,qos);
|
||||
}
|
||||
},
|
||||
on: function(a,b){
|
||||
client.on(a,b);
|
||||
},
|
||||
once: function(a,b){
|
||||
client.once(a,b);
|
||||
},
|
||||
connect: function() {
|
||||
if (client && !client.isConnected() && !connecting) {
|
||||
connecting = true;
|
||||
client.connect(options);
|
||||
}
|
||||
},
|
||||
disconnect: function() {
|
||||
this._instances -= 1;
|
||||
if (this._instances == 0) {
|
||||
client.disconnect();
|
||||
client = null;
|
||||
delete connections[id];
|
||||
}
|
||||
}
|
||||
};
|
||||
client.on('connect',function() {
|
||||
if (client) {
|
||||
util.log('[mqtt] ['+uid+'] connected to broker tcp://'+broker+':'+port);
|
||||
connecting = false;
|
||||
for (var s in subscriptions) {
|
||||
var topic = subscriptions[s].topic;
|
||||
var qos = subscriptions[s].qos;
|
||||
var callback = subscriptions[s].callback;
|
||||
client.subscribe(topic,qos);
|
||||
}
|
||||
//console.log("connected - publishing",queue.length,"messages");
|
||||
while(queue.length) {
|
||||
var msg = queue.shift();
|
||||
//console.log(msg);
|
||||
client.publish(msg.topic,msg.payload,msg.qos,msg.retain);
|
||||
}
|
||||
}
|
||||
});
|
||||
client.on('connectionlost', function(err) {
|
||||
util.log('[mqtt] ['+uid+'] connection lost to broker tcp://'+broker+':'+port);
|
||||
connecting = false;
|
||||
setTimeout(function() {
|
||||
obj.connect();
|
||||
}, settings.mqttReconnectTime||5000);
|
||||
});
|
||||
client.on('disconnect', function() {
|
||||
connecting = false;
|
||||
util.log('[mqtt] ['+uid+'] disconnected from broker tcp://'+broker+':'+port);
|
||||
});
|
||||
|
||||
return obj
|
||||
}();
|
||||
}
|
||||
connections[id]._instances += 1;
|
||||
return connections[id];
|
||||
}
|
||||
};
|
||||
@@ -1,223 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="switch">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
If msg.<input type="text" id="node-input-property" style="width: 200px;"/>
|
||||
</div>
|
||||
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
|
||||
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
|
||||
<ol id="node-input-rule-container" style=" list-style-type:none; margin: 0;"></ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> rule</a>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<select id="node-input-checkall" style="width:100%; margin-right:5px;">
|
||||
<option value="true">checking all rules</option>
|
||||
<option value="false">stopping after first match</option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="switch">
|
||||
<p>A simple function node to route messages based on its properties.</p>
|
||||
<p>When a message arrives, the selected property is evaluated against each
|
||||
of the defined rules. The message is then sent to the output of <i>all</i>
|
||||
rules that pass.</p>
|
||||
<p>Note: the <i>otherwise</i> rule applies as a "not any of" the rules preceding it.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('switch', {
|
||||
color: "#E2D96E",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload", required:true},
|
||||
rules: {value:[{t:"eq", v:""}]},
|
||||
checkall: {value:"true", required:true},
|
||||
outputs: {value:1}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "switch.png",
|
||||
label: function() {
|
||||
return this.name||"switch";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
|
||||
var operators = [
|
||||
{v:"eq",t:"=="},
|
||||
{v:"neq",t:"!="},
|
||||
{v:"lt",t:"<"},
|
||||
{v:"lte",t:"<="},
|
||||
{v:"gt",t:">"},
|
||||
{v:"gte",t:">="},
|
||||
{v:"btwn",t:"is between"},
|
||||
{v:"cont",t:"contains"},
|
||||
{v:"regex",t:"matches regex"},
|
||||
{v:"true",t:"is true"},
|
||||
{v:"false",t:"is false"},
|
||||
{v:"null",t:"is null"},
|
||||
{v:"nnull",t:"is not null"},
|
||||
{v:"else",t:"otherwise"}
|
||||
];
|
||||
|
||||
function generateRule(i,rule) {
|
||||
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
|
||||
var row = $('<div/>').appendTo(container);
|
||||
var row2 = $('<div/>',{style:"padding-top: 5px; text-align: right;"}).appendTo(container);
|
||||
$('<i style="color: #eee; cursor: move;" class="node-input-rule-handle fa fa-bars"></i>').appendTo(row);
|
||||
|
||||
var selectField = $('<select/>',{style:"width:120px; margin-left: 5px; text-align: center;"}).appendTo(row);
|
||||
for (var d in operators) {
|
||||
selectField.append($("<option></option>").val(operators[d].v).text(operators[d].t));
|
||||
}
|
||||
|
||||
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px; width: 145px;"}).appendTo(row);
|
||||
var btwnField = $('<span/>').appendTo(row);
|
||||
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px; width: 50px;"}).appendTo(btwnField);
|
||||
btwnField.append(" and ");
|
||||
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 50px;margin-left:2px;"}).appendTo(btwnField);
|
||||
|
||||
var finalspan = $('<span/>',{style:"float: right; margin-top: 3px;margin-right: 10px;"}).appendTo(row);
|
||||
finalspan.append(' → <span class="node-input-rule-index">'+i+'</span> ');
|
||||
|
||||
var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
|
||||
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
|
||||
|
||||
selectField.change(function() {
|
||||
var type = selectField.children("option:selected").val();
|
||||
if (type.length < 4) {
|
||||
selectField.css({"width":"60px"});
|
||||
} else if (type === "regex") {
|
||||
selectField.css({"width":"147px"});
|
||||
} else {
|
||||
selectField.css({"width":"120px"});
|
||||
}
|
||||
if (type === "btwn") {
|
||||
valueField.hide();
|
||||
btwnField.show();
|
||||
} else {
|
||||
btwnField.hide();
|
||||
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
|
||||
valueField.hide();
|
||||
} else {
|
||||
valueField.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
deleteButton.click(function() {
|
||||
container.css({"background":"#fee"});
|
||||
container.fadeOut(300, function() {
|
||||
$(this).remove();
|
||||
$("#node-input-rule-container").children().each(function(i) {
|
||||
$(this).find(".node-input-rule-index").html(i+1);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
$("#node-input-rule-container").append(container);
|
||||
|
||||
selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
|
||||
if (rule.t == "btwn") {
|
||||
btwnValueField.val(rule.v);
|
||||
btwnValue2Field.val(rule.v2);
|
||||
} else if (typeof rule.v != "undefined") {
|
||||
valueField.val(rule.v);
|
||||
}
|
||||
selectField.change();
|
||||
}
|
||||
|
||||
$("#node-input-add-rule").click(function() {
|
||||
generateRule($("#node-input-rule-container").children().length+1,{t:"",v:"",v2:""});
|
||||
$("#node-input-rule-container-div").scrollTop($("#node-input-rule-container-div").get(0).scrollHeight);
|
||||
});
|
||||
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
var rule = this.rules[i];
|
||||
generateRule(i+1,rule);
|
||||
}
|
||||
|
||||
function switchDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-rule-container-div").css("height",height+"px");
|
||||
};
|
||||
|
||||
$( "#node-input-rule-container" ).sortable({
|
||||
axis: "y",
|
||||
update: function( event, ui ) {
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
rules.each(function(i) {
|
||||
$(this).find(".node-input-rule-index").html(i+1);
|
||||
});
|
||||
},
|
||||
handle:".node-input-rule-handle",
|
||||
cursor: "move"
|
||||
});
|
||||
$( "#node-input-rule-container .node-input-rule-handle" ).disableSelection();
|
||||
|
||||
$( "#dialog" ).on("dialogresize", switchDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-switch');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
switchDialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
$( "#dialog" ).off("dialogresize",switchDialogResize);
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
var ruleset;
|
||||
var node = this;
|
||||
node.rules= [];
|
||||
rules.each(function(i) {
|
||||
var rule = $(this);
|
||||
var type = rule.find("select option:selected").val();
|
||||
var r = {t:type};
|
||||
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
|
||||
if (type === "btwn") {
|
||||
r.v = rule.find(".node-input-rule-btwn-value").val();
|
||||
r.v2 = rule.find(".node-input-rule-btwn-value2").val();
|
||||
} else {
|
||||
r.v = rule.find(".node-input-rule-value").val();
|
||||
}
|
||||
}
|
||||
node.rules.push(r);
|
||||
});
|
||||
node.outputs = node.rules.length;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var operators = {
|
||||
'eq': function(a, b) { return a == b; },
|
||||
'neq': function(a, b) { return a != b; },
|
||||
'lt': function(a, b) { return a < b; },
|
||||
'lte': function(a, b) { return a <= b; },
|
||||
'gt': function(a, b) { return a > b; },
|
||||
'gte': function(a, b) { return a >= b; },
|
||||
'btwn': function(a, b, c) { return a >= b && a <= c; },
|
||||
'cont': function(a, b) { return (a + "").indexOf(b) != -1; },
|
||||
'regex': function(a, b) { return (a + "").match(new RegExp(b)); },
|
||||
'true': function(a) { return a === true; },
|
||||
'false': function(a) { return a === false; },
|
||||
'null': function(a) { return typeof a == "undefined"; },
|
||||
'nnull': function(a) { return typeof a != "undefined"; },
|
||||
'else': function(a) { return a === true; }
|
||||
};
|
||||
|
||||
function SwitchNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.rules = n.rules;
|
||||
this.property = n.property;
|
||||
this.checkall = n.checkall || "true";
|
||||
var propertyParts = n.property.split(".");
|
||||
var node = this;
|
||||
|
||||
for (var i=0; i<this.rules.length; i+=1) {
|
||||
var rule = this.rules[i];
|
||||
if (!isNaN(Number(rule.v))) {
|
||||
rule.v = Number(rule.v);
|
||||
rule.v2 = Number(rule.v2);
|
||||
}
|
||||
}
|
||||
|
||||
this.on('input', function (msg) {
|
||||
var onward = [];
|
||||
try {
|
||||
var prop = propertyParts.reduce(function (obj, i) {
|
||||
return obj[i]
|
||||
}, msg);
|
||||
var elseflag = true;
|
||||
for (var i=0; i<node.rules.length; i+=1) {
|
||||
var rule = node.rules[i];
|
||||
var test = prop;
|
||||
if (rule.t == "else") { test = elseflag; elseflag = true; }
|
||||
if (operators[rule.t](test,rule.v, rule.v2)) {
|
||||
onward.push(msg);
|
||||
elseflag = false;
|
||||
if (node.checkall == "false") { break; }
|
||||
} else {
|
||||
onward.push(null);
|
||||
}
|
||||
}
|
||||
this.send(onward);
|
||||
} catch(err) {
|
||||
node.warn(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("switch", SwitchNode);
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="change">
|
||||
<div>
|
||||
<select id="node-input-action" style="width:95%; margin-right:5px;">
|
||||
<option value="replace">Set the value of the message property</option>
|
||||
<option value="change">Search/replace the value of the message property</option>
|
||||
<option value="delete">Delete the message property</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" style="padding-top:10px;" id="node-prop1-row">
|
||||
<label for="node-input-property">called</label> msg.<input type="text" id="node-input-property" style="width: 63%;"/>
|
||||
</div>
|
||||
<div class="form-row" id="node-from-row">
|
||||
<label for="node-input-from" id="node-input-f"></label>
|
||||
<input type="text" id="node-input-from" placeholder="this"/>
|
||||
</div>
|
||||
<div class="form-row" id="node-to-row">
|
||||
<label for="node-input-to" id="node-input-t"></label>
|
||||
<input type="text" id="node-input-to" placeholder="that"/>
|
||||
</div>
|
||||
<div class="form-row" id="node-reg-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-reg" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-reg" style="width: 70%;">Use regular expressions</label>
|
||||
</div>
|
||||
<div class="form-tips" id="node-tip"></div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="change">
|
||||
<p>A simple function node to set, replace or delete properties of a message.</p>
|
||||
<p>When a message arrives, the selected property is modified by the defined rules.
|
||||
The message is then sent to the output.</p>
|
||||
<p><b>Note:</b> Set and replace only operate using <b>strings</b>. Anything else will be passed straight through.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('change', {
|
||||
color: "#E2D96E",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
action: {value:"replace",required:true},
|
||||
property: {value:"payload",required:true},
|
||||
from: {value:"",validate: function(v) {
|
||||
if (this.action === "change" && !this.from) {
|
||||
return false;
|
||||
} else if (this.action === "change" && this.reg) {
|
||||
try {
|
||||
var re = new RegExp(this.from, "g");
|
||||
return true;
|
||||
} catch(err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}},
|
||||
to: {value:""},
|
||||
reg: {value:false},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "swap.png",
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.action === "replace") {
|
||||
return "set msg."+this.property;
|
||||
} else if (this.action === "change") {
|
||||
return "replace msg."+this.property;
|
||||
} else {
|
||||
return this.action+" msg."+this.property
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.reg === null) { $("#node-input-reg").prop('checked', true); }
|
||||
$("#node-input-action").change( function() {
|
||||
var a = $("#node-input-action").val();
|
||||
if (a === "replace") {
|
||||
$("#node-input-todo").html("called");
|
||||
//$("#node-input-f").html("name");
|
||||
$("#node-input-t").html("to");
|
||||
$("#node-from-row").hide();
|
||||
$("#node-to-row").show();
|
||||
$("#node-reg-row").hide();
|
||||
$("#node-tip").show();
|
||||
$("#node-tip").html("Tip: expects a new property name and either a fixed value OR the full name of another message property eg: msg.sentiment.score");
|
||||
}
|
||||
if (a === "delete") {
|
||||
$("#node-input-todo").html("called");
|
||||
//$("#node-input-f").html("called");
|
||||
//$("#node-input-t").html("to");
|
||||
$("#node-from-row").hide();
|
||||
$("#node-to-row").hide();
|
||||
$("#node-reg-row").hide();
|
||||
$("#node-tip").hide();
|
||||
}
|
||||
if (a === "change") {
|
||||
$("#node-input-todo").html("called");
|
||||
$("#node-input-f").html("Search for");
|
||||
$("#node-input-t").html("replace with");
|
||||
$("#node-from-row").show();
|
||||
$("#node-to-row").show();
|
||||
$("#node-reg-row").show();
|
||||
$("#node-tip").show();
|
||||
$("#node-tip").html("Tip: only works on string properties. If regular expressions are used, the <i>replace with</i> field can contain capture results, eg $1.");
|
||||
}
|
||||
//if (a === "replace") {
|
||||
// $("#node-input-todo").html("called");
|
||||
// //$("#node-input-f").html("with");
|
||||
// $("#node-input-t").html("with");
|
||||
// $("#node-from-row").hide();
|
||||
// $("#node-to-row").show();
|
||||
// $("#node-tip").html("Tip: accepts either a fixed value OR the full name of another msg.property eg: msg.sentiment.score");
|
||||
//}
|
||||
});
|
||||
$("#node-input-action").change();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
function ChangeNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.action = n.action;
|
||||
this.property = n.property || "";
|
||||
this.from = n.from || "";
|
||||
this.to = n.to || "";
|
||||
this.reg = (n.reg === null || n.reg);
|
||||
var node = this;
|
||||
if (node.reg === false) {
|
||||
this.from = this.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
|
||||
this.on('input', function(msg) {
|
||||
var propertyParts;
|
||||
var depth = 0;
|
||||
|
||||
if (node.action === "change") {
|
||||
try {
|
||||
node.re = new RegExp(this.from, "g");
|
||||
} catch (e) {
|
||||
node.error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
propertyParts = node.property.split(".");
|
||||
try {
|
||||
propertyParts.reduce(function(obj, i) {
|
||||
var to = node.to;
|
||||
// Set msg from property to another msg property
|
||||
if (node.action === "replace" && node.to.indexOf("msg.") === 0) {
|
||||
var parts = to.substring(4);
|
||||
var msgPropParts = parts.split(".");
|
||||
try {
|
||||
msgPropParts.reduce(function(ob, j) {
|
||||
to = (typeof ob[j] !== "undefined" ? ob[j] : undefined);
|
||||
return to;
|
||||
}, msg);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
if (++depth === propertyParts.length) {
|
||||
if (node.action === "change") {
|
||||
if (typeof obj[i] === "string") {
|
||||
obj[i] = obj[i].replace(node.re, node.to);
|
||||
}
|
||||
} else if (node.action === "replace") {
|
||||
if (typeof to === "undefined") {
|
||||
delete(obj[i]);
|
||||
} else {
|
||||
obj[i] = to;
|
||||
}
|
||||
} else if (node.action === "delete") {
|
||||
delete(obj[i]);
|
||||
}
|
||||
} else {
|
||||
// to property doesn't exist, don't create empty object
|
||||
if (typeof to === "undefined") {
|
||||
return;
|
||||
// setting a non-existent multilevel object, create empty parent
|
||||
} else if (!obj[i]) {
|
||||
obj[i] = {};
|
||||
}
|
||||
return obj[i];
|
||||
}
|
||||
}, msg);
|
||||
} catch (err) {}
|
||||
node.send(msg);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("change", ChangeNode);
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="range">
|
||||
<div class="form-row">
|
||||
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> Action</label>
|
||||
<select id="node-input-action" style="width:70%; margin-right:5px;">
|
||||
<option value="scale">Scale msg.payload</option>
|
||||
<option value="clamp">Scale and limit to the target range</option>
|
||||
<option value="roll">Scale and wrap within the target range</option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row"><i class="fa fa-sign-in"></i> Map the input range:</div>
|
||||
<div class="form-row"><label></label>
|
||||
from: <input type="text" id="node-input-minin" placeholder="e.g. 0" style="width:100px;"/>
|
||||
to: <input type="text" id="node-input-maxin" placeholder="e.g. 99" style="width:100px;"/>
|
||||
</div>
|
||||
<div class="form-row"><i class="fa fa-sign-out"></i> to the result range:</div>
|
||||
<div class="form-row"><label></label>
|
||||
from: <input type="text" id="node-input-minout" placeholder="e.g. 0" style="width:100px;"/>
|
||||
to: <input type="text" id="node-input-maxout" placeholder="e.g. 255" style="width:100px;"/>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row"><label></label>
|
||||
<input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label style="width: auto;" for="node-input-round">Round result to the nearest integer?</label></input>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips" id="node-tip">Tip: This node ONLY works with numbers.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="range">
|
||||
<p>A simple function node to remap numeric input values to another scale.</p>
|
||||
<p>Currently only does a linear scaling.</p>
|
||||
<p><b>Note:</b> This only operates on <b>numbers</b>. Anything else will try to be made into a number and rejected if that fails.</p>
|
||||
<p><i>Scale and limit to target range</i> means that the result will never be outside the range specified within the result range.</p>
|
||||
<p><i>Scale and wrap within the target range</i> means that the result will essentially be a "modulo-style" wrap-around within the result range.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('range', {
|
||||
color: "#E2D96E",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
minin: {value:"",required:true,validate:RED.validators.number()},
|
||||
maxin: {value:"",required:true,validate:RED.validators.number()},
|
||||
minout: {value:"",required:true,validate:RED.validators.number()},
|
||||
maxout: {value:"",required:true,validate:RED.validators.number()},
|
||||
action: {value:"scale"},
|
||||
round: {value:false},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "range.png",
|
||||
label: function() {
|
||||
return this.name || "range";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function RangeNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.action = n.action;
|
||||
this.round = n.round || false;
|
||||
this.minin = Number(n.minin);
|
||||
this.maxin = Number(n.maxin);
|
||||
this.minout = Number(n.minout);
|
||||
this.maxout = Number(n.maxout);
|
||||
var node = this;
|
||||
|
||||
this.on('input', function (msg) {
|
||||
var n = Number(msg.payload);
|
||||
if (!isNaN(n)) {
|
||||
if (node.action == "clamp") {
|
||||
if (n < node.minin) { n = node.minin; }
|
||||
if (n > node.maxin) { n = node.maxin; }
|
||||
}
|
||||
if (node.action == "roll") {
|
||||
if (n >= node.maxin) { n = (n - node.minin) % (node.maxin - node.minin) + node.minin; }
|
||||
if (n < node.minin) { n = (n - node.minin) % (node.maxin - node.minin) + node.maxin; }
|
||||
}
|
||||
msg.payload = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout;
|
||||
if (node.round) { msg.payload = Math.round(msg.payload); }
|
||||
node.send(msg);
|
||||
}
|
||||
else { node.log("Not a number: "+msg.payload); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("range", RangeNode);
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
|
||||
<!--
|
||||
Copyright 2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="csv">
|
||||
<div class="form-row">
|
||||
<label for="node-input-temp"><i class="fa fa-list"></i> Columns</label>
|
||||
<input type="text" id="node-input-temp" placeholder="comma-separated column names">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-select-sep"><i class="fa fa-text-width"></i> Separator</label>
|
||||
<select style="width: 150px" id="node-input-select-sep">
|
||||
<option value=",">comma</option>
|
||||
<option value="\t">tab</option>
|
||||
<option value=" ">space</option>
|
||||
<option value=";">semicolon</option>
|
||||
<option value=":">colon</option>
|
||||
<option value="#">hashtag</option>
|
||||
<option value="">other...</option>
|
||||
</select>
|
||||
<input style="width: 40px;" type="text" id="node-input-sep" pattern=".">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<hr align="middle"/>
|
||||
<div class="form-row">
|
||||
<label style="width: 100%;"><i class="fa fa-gears"></i> CSV-to-Object options</label>
|
||||
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Input</label>
|
||||
<input style="width: 30px" type="checkbox" id="node-input-hdrin"><label style="width: auto;" for="node-input-hdrin">first row contains column names</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-out"></i> Output</label>
|
||||
<select type="text" id="node-input-multi" style="width: 250px;">
|
||||
<option value="one">a message per row</option>
|
||||
<option value="mult">a single message [array]</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr align="middle"/>
|
||||
<div class="form-row">
|
||||
<label style="width: 100%;"><i class="fa fa-gears"></i> Object-to-CSV options</label>
|
||||
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Output</label>
|
||||
<input style="width: 30px" type="checkbox" id="node-input-hdrout"><label style="width: auto;" for="node-input-hdrout">include column name row</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-align-left"></i> Newline</label>
|
||||
<select style="width: 150px" id="node-input-ret">
|
||||
<option value='\n'>Linux (\n)</option>
|
||||
<option value='\r'>Mac (\r)</option>
|
||||
<option value='\r\n'>Windows (\r\n)</option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="csv">
|
||||
<p>A function that parses the <b>msg.payload</b> to convert csv to/from a javascript object.
|
||||
Places the result in the payload.</p>
|
||||
<p>If the input is a string it tries to parse it as CSV and creates a javascript object.</p>
|
||||
<p>If the input is a javascript object it tries to build a CSV string.</p>
|
||||
<p>The columns template should contain an ordered list of column headers. For csv input these become the property names.
|
||||
For csv output these specify the properties to extract from the object and the order for the csv.</p>
|
||||
<p><b>Note:</b> the columns should always be specified comma separated - even if another separator is chosen for the data.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('csv',{
|
||||
category: 'function',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
|
||||
//quo: {value:'"',required:true},
|
||||
hdrin: {value:""},
|
||||
hdrout: {value:""},
|
||||
multi: {value:"one",required:true},
|
||||
ret: {value:'\\n'},
|
||||
temp: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "arrow-in.png",
|
||||
label: function() {
|
||||
return this.name||"csv";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.sep == "," || this.sep == "\\t" || this.sep == ";" || this.sep == ":" || this.sep == " " || this.sep == "#") {
|
||||
$("#node-input-select-sep").val(this.sep);
|
||||
$("#node-input-sep").hide();
|
||||
} else {
|
||||
$("#node-input-select-sep").val("");
|
||||
$("#node-input-sep").val(this.sep);
|
||||
$("#node-input-sep").show();
|
||||
}
|
||||
$("#node-input-select-sep").change(function() {
|
||||
var v = $("#node-input-select-sep option:selected").val();
|
||||
$("#node-input-sep").val(v);
|
||||
if (v == "") {
|
||||
$("#node-input-sep").val("");
|
||||
$("#node-input-sep").show().focus();
|
||||
} else {
|
||||
$("#node-input-sep").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,158 +0,0 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function CSVNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.template = n.temp.split(",");
|
||||
this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
|
||||
this.quo = '"';
|
||||
this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
|
||||
this.winflag = (this.ret === "\r\n");
|
||||
this.lineend = "\n";
|
||||
this.multi = n.multi || "one";
|
||||
this.hdrin = n.hdrin || false;
|
||||
this.hdrout = n.hdrout || false;
|
||||
this.goodtmpl = true;
|
||||
var node = this;
|
||||
|
||||
// pass in an array of column names to be trimed, de-quoted and retrimed
|
||||
var clean = function(col) {
|
||||
for (var t = 0; t < col.length; t++) {
|
||||
col[t] = col[t].trim(); // remove leading and trailing whitespace
|
||||
if (col[t].charAt(0) === '"' && col[t].charAt(col[t].length -1) === '"') {
|
||||
// remove leading and trailing quotes (if they exist) - and remove whitepace again.
|
||||
col[t] = col[t].substr(1,col[t].length -2).trim();
|
||||
}
|
||||
}
|
||||
if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
|
||||
else { node.goodtmpl = true; }
|
||||
return col;
|
||||
}
|
||||
node.template = clean(node.template);
|
||||
|
||||
this.on("input", function(msg) {
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
if (typeof msg.payload == "object") { // convert object to CSV string
|
||||
try {
|
||||
var ou = "";
|
||||
if (node.hdrout) {
|
||||
ou += node.template.join(node.sep) + node.ret;
|
||||
}
|
||||
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
|
||||
for (var s = 0; s < msg.payload.length; s++) {
|
||||
for (var t=0; t < node.template.length; t++) {
|
||||
|
||||
// aaargh - resorting to eval here - but fairly contained front and back.
|
||||
var p = RED.util.ensureString(eval("msg.payload[s]."+node.template[t]));
|
||||
|
||||
if (p === "undefined") { p = ""; }
|
||||
if (p.indexOf(node.sep) != -1) { // add quotes if any "commas"
|
||||
ou += node.quo + p + node.quo + node.sep;
|
||||
}
|
||||
else if (p.indexOf(node.quo) != -1) { // add double quotes if any quotes
|
||||
p = p.replace(/"/g, '""');
|
||||
ou += node.quo + p + node.quo + node.sep;
|
||||
}
|
||||
else { ou += p + node.sep; } // otherwise just add
|
||||
}
|
||||
ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline"
|
||||
}
|
||||
node.send({payload:ou});
|
||||
}
|
||||
catch(e) { node.log(e); }
|
||||
}
|
||||
else if (typeof msg.payload == "string") { // convert CSV string to object
|
||||
try {
|
||||
var f = true; // flag to indicate if inside or outside a pair of quotes true = outside.
|
||||
var j = 0; // pointer into array of template items
|
||||
var k = [""]; // array of data for each of the template items
|
||||
var o = {}; // output object to build up
|
||||
var a = []; // output array is needed for multiline option
|
||||
var first = true; // is this the first line
|
||||
var tmp = "";
|
||||
|
||||
// For now we are just going to assume that any \r or \n means an end of line...
|
||||
// got to be a weird csv that has singleton \r \n in it for another reason...
|
||||
|
||||
// Now process the whole file/line
|
||||
for (var i = 0; i < msg.payload.length; i++) {
|
||||
if ((node.hdrin === true) && first) { // if the template is in the first line
|
||||
if ((msg.payload[i] === "\n")||(msg.payload[i] === "\r")) { // look for first line break
|
||||
node.template = clean(tmp.split(node.sep));
|
||||
first = false;
|
||||
}
|
||||
else { tmp += msg.payload[i]; }
|
||||
}
|
||||
else {
|
||||
if (msg.payload[i] === node.quo) { // if it's a quote toggle inside or outside
|
||||
f = !f;
|
||||
if (msg.payload[i-1] === node.quo) { k[j] += '\"'; } // if it's a quotequote then it's actually a quote
|
||||
if ((msg.payload[i-1] !== node.sep) && (msg.payload[i+1] !== node.sep)) { k[j] += msg.payload[i]; }
|
||||
}
|
||||
else if ((msg.payload[i] === node.sep) && f) { // if we are outside of quote (ie valid separator
|
||||
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
|
||||
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "" ) ) {
|
||||
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
|
||||
o[node.template[j]] = k[j];
|
||||
}
|
||||
j += 1;
|
||||
k[j] = "";
|
||||
}
|
||||
else if (f && ((msg.payload[i] === "\n") || (msg.payload[i] === "\r"))) { // handle multiple lines
|
||||
//console.log(j,k,o,k[j]);
|
||||
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
|
||||
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
|
||||
else { k[j].replace(/\r$/,''); }
|
||||
o[node.template[j]] = k[j];
|
||||
}
|
||||
if (JSON.stringify(o) !== "{}") { // don't send empty objects
|
||||
if (node.multi === "one") { node.send({payload:o}); } // either send
|
||||
else { a.push(o); } // or add to the array
|
||||
}
|
||||
j = 0;
|
||||
k = [""];
|
||||
o = {};
|
||||
}
|
||||
else { // just add to the part of the message
|
||||
k[j] += msg.payload[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finished so finalize and send anything left
|
||||
//console.log(j,k,o,k[j]);
|
||||
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
|
||||
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
|
||||
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
|
||||
else { k[j].replace(/\r$/,''); }
|
||||
o[node.template[j]] = k[j];
|
||||
}
|
||||
msg.payload = o;
|
||||
if (JSON.stringify(o) !== "{}") { // don't send empty objects
|
||||
if (node.multi === "one") { node.send({payload:o}); } // either send
|
||||
else { a.push(o); } // or add to the aray
|
||||
}
|
||||
if (node.multi !== "one") { node.send({payload:a}); } // finally send the array
|
||||
}
|
||||
catch(e) { node.log(e); }
|
||||
}
|
||||
else { node.log("This node only handles csv strings or js objects."); }
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("csv",CSVNode);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<!--
|
||||
Copyright 2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="html">
|
||||
<div class="form-row">
|
||||
<label for="node-input-tag"><i class="fa fa-filter"></i> Select</label>
|
||||
<input type="text" id="node-input-tag" placeholder="h1">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-ret"><i class="fa fa-sign-out"></i> Output</label>
|
||||
<select id="node-input-ret" style="width:73% !important">
|
||||
<option value="html">the html content of the elements</option>
|
||||
<option value="text">only the text content of the elements</option>
|
||||
<!-- <option value="attr">an object of any attributes</option> -->
|
||||
<!-- <option value="val">return the value from a form element</option> -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-as"> </label>
|
||||
<select id="node-input-as" style="width:73% !important">
|
||||
<option value="single">as a single message containing an array</option>
|
||||
<option value="multi">as multiple messages, one for each element</option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: The <b>Select</b> value is a <a href="http://api.jquery.com/category/selectors/" target="_new"><i><u>jQuery</u></i></a> style selector.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="html">
|
||||
<p>Extracts elements from an html document held in <b>msg.payload</b> using a selector.</p>
|
||||
<p>The selector uses <a href=="https://github.com/cheeriojs/cheerio/blob/master/Readme.md" target="_new">Cheerio</a>
|
||||
which uses the <a href="https://github.com/fb55/CSSselect" target="_new">CSS selector</a> syntax.</p>
|
||||
<p>The result is either a single message with a payload containing an array of the matched elements, or multiple
|
||||
messages that each contain a matched element.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('html',{
|
||||
category: 'function',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
tag: {value:""},
|
||||
ret: {value:"html"},
|
||||
as: {value:"single"},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "jq.png",
|
||||
label: function() {
|
||||
return this.name||this.tag||"html";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var cheerio = require('cheerio');
|
||||
|
||||
function CheerioNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.tag = n.tag || "h1";
|
||||
this.ret = n.ret || "html";
|
||||
this.as = n.as || "single";
|
||||
var node = this;
|
||||
this.on("input", function(msg) {
|
||||
try {
|
||||
var $ = cheerio.load(msg.payload);
|
||||
var pay = [];
|
||||
$(node.tag).each(function() {
|
||||
if (node.as === "multi") {
|
||||
var pay2 = null;
|
||||
if (node.ret === "html") { pay2 = $(this).html(); }
|
||||
if (node.ret === "text") { pay2 = $(this).text(); }
|
||||
//if (node.ret === "attr") { pay2 = $(this)[0]["attribs"]; }
|
||||
//if (node.ret === "val") { pay2 = $(this).val(); }
|
||||
if (pay2) {
|
||||
msg.payload = pay2;
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
if (node.as === "single") {
|
||||
if (node.ret === "html") { pay.push( $(this).html() ); }
|
||||
if (node.ret === "text") { pay.push( $(this).text() ); }
|
||||
//if (node.ret === "attr") { pay.push( $(this)[0]["attribs"] ); }
|
||||
//if (node.ret === "val") { pay.push( $(this).val() ); }
|
||||
}
|
||||
});
|
||||
if ((node.as === "single") && (pay.length !== 0)) {
|
||||
msg.payload = pay;
|
||||
node.send(msg);
|
||||
}
|
||||
} catch (error) {
|
||||
node.log('Error: '+error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("html",CheerioNode);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<!--
|
||||
Copyright 2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="json">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="json">
|
||||
<p>A function that parses the <b>msg.payload</b> to convert a JSON string to/from a javascript object. Places the result back into the payload.</p>
|
||||
<p>If the input is a JSON string it tries to parse it to a javascript object.</p>
|
||||
<p>If the input is a javascript object it creates a JSON string.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('json',{
|
||||
category: 'function',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "arrow-in.png",
|
||||
label: function() {
|
||||
return this.name||"json";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
|
||||
function JSONNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.on("input", function(msg) {
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
if (typeof msg.payload === "string") {
|
||||
try {
|
||||
msg.payload = JSON.parse(msg.payload);
|
||||
node.send(msg);
|
||||
}
|
||||
catch(e) { node.log(e+ "\n"+msg.payload); }
|
||||
}
|
||||
else if (typeof msg.payload === "object") {
|
||||
if (!Buffer.isBuffer(msg.payload) ) {
|
||||
if (!util.isArray(msg.payload)) {
|
||||
msg.payload = JSON.stringify(msg.payload);
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
else { node.log("dropped: "+msg.payload); }
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("json",JSONNode);
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<!--
|
||||
Copyright 2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="xml">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name" style="width:280px !important">
|
||||
</div>
|
||||
<div class="form-row" id="advanced">
|
||||
</div>
|
||||
<div id="advanced-options">
|
||||
<div class="form-row">
|
||||
<i class="fa fa-key"></i> Represent XML tag attributes as a property named <input type="text" id="node-input-attr" style="width:20px !important">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<i class="fa fa-key"></i> Prefix to access character content <input type="text" id="node-input-chr" style="width:20px !important">
|
||||
</div>
|
||||
<div class="form-tips">There is no simple way to convert XML attributes to JSON
|
||||
so the approach taken here is to add a property, named $ by default, to the JSON structure.</div>
|
||||
</div>
|
||||
<script> {
|
||||
var showadvanced = showadvanced || true;
|
||||
var showall = function() {
|
||||
showadvanced = !showadvanced;
|
||||
if (showadvanced) {
|
||||
$("#advanced-options").show();
|
||||
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-minus-square"></i> Advanced options</label>');
|
||||
}
|
||||
else {
|
||||
$("#advanced-options").hide();
|
||||
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-plus-square"></i> Advanced options ...</label>');
|
||||
}
|
||||
};
|
||||
showall();
|
||||
$("#advanced").click( function() { showall(); });
|
||||
} </script>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="xml">
|
||||
<p>A function that parses the <b>msg.payload</b> to convert xml to/from a javascript object. Places the result in the payload.</p>
|
||||
<p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
|
||||
<p>If the input is a javascript object it tries to build an XML string.</p>
|
||||
<p>See <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a> for more information.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('xml',{
|
||||
category: 'function',
|
||||
color:"#DEBD5C",
|
||||
defaults: {
|
||||
attr: {value:'$',required:true},
|
||||
chr: {value:'_',required:true},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "arrow-in.png",
|
||||
label: function() {
|
||||
return this.name||"xml";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var xml2js = require('xml2js');
|
||||
var parseString = xml2js.parseString;
|
||||
var builder = new xml2js.Builder({renderOpts:{pretty:false}});
|
||||
|
||||
function XMLNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.attrkey = n.attr || '$';
|
||||
this.charkey = n.chr || '_';
|
||||
var node = this;
|
||||
this.on("input", function(msg) {
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
if (typeof msg.payload == "object") {
|
||||
msg.payload = builder.buildObject(msg.payload);
|
||||
node.send(msg);
|
||||
}
|
||||
else if (typeof msg.payload == "string") {
|
||||
parseString(msg.payload, {strict:true,async:true,attrkey:node.attrkey,charkey:node.charkey}, function (err, result) {
|
||||
if (err) { node.error(err); }
|
||||
else {
|
||||
msg.payload = result;
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
else { node.log("This node only handles xml strings or js objects."); }
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("xml",XMLNode);
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="twitter-credentials">
|
||||
<div class="form-row" id="node-config-twitter-row"></div>
|
||||
<input type="hidden" id="node-config-input-screen_name">
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var twitterConfigNodeId = null;
|
||||
var twitterConfigNodeIntervalId = null;
|
||||
|
||||
function showTwitterAuthStart() {
|
||||
var pathname = document.location.pathname;
|
||||
if (pathname.slice(-1) != "/") {
|
||||
pathname += "/";
|
||||
}
|
||||
var callback = encodeURIComponent(location.protocol+"//"+location.hostname+":"+location.port+pathname+"twitter-credentials/"+twitterConfigNodeId+"/auth/callback");
|
||||
$("#node-config-dialog-ok").button("disable");
|
||||
$("#node-config-twitter-row").html('<div style="text-align: center; margin-top: 20px; "><a class="btn" id="node-config-twitter-start" href="twitter-credentials/'+twitterConfigNodeId+'/auth?callback='+callback+'" target="_blank">Click here to authenticate with Twitter.</a></div>');
|
||||
$("#node-config-twitter-start").click(function() {
|
||||
twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000);
|
||||
});
|
||||
}
|
||||
function updateTwitterScreenName(sn) {
|
||||
$("#node-config-input-screen_name").val(sn);
|
||||
$("#node-config-twitter-row").html('<label><i class="fa fa-user"></i> Twitter ID</label><span class="input-xlarge uneditable-input">'+sn+'</span>');
|
||||
}
|
||||
function pollTwitterCredentials(e) {
|
||||
$.getJSON('credentials/twitter-credentials/'+twitterConfigNodeId,function(data) {
|
||||
if (data.screen_name) {
|
||||
updateTwitterScreenName(data.screen_name);
|
||||
twitterConfigNodeIntervalId = null;
|
||||
$("#node-config-dialog-ok").button("enable");
|
||||
} else {
|
||||
twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000);
|
||||
}
|
||||
})
|
||||
}
|
||||
RED.nodes.registerType('twitter-credentials',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
screen_name: {value:""}
|
||||
},
|
||||
credentials: {
|
||||
screen_name: {type:"text"},
|
||||
access_token: {type: "password"},
|
||||
access_token_secret: {type:"password"}
|
||||
},
|
||||
|
||||
label: function() {
|
||||
return this.screen_name;
|
||||
},
|
||||
exportable: false,
|
||||
oneditprepare: function() {
|
||||
twitterConfigNodeId = this.id;
|
||||
if (!this.screen_name || this.screen_name == "") {
|
||||
showTwitterAuthStart();
|
||||
} else {
|
||||
if (this.credentials.screen_name) {
|
||||
updateTwitterScreenName(this.credentials.screen_name);
|
||||
} else {
|
||||
showTwitterAuthStart();
|
||||
}
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (twitterConfigNodeIntervalId) {
|
||||
window.clearTimeout(twitterConfigNodeIntervalId);
|
||||
}
|
||||
},
|
||||
oneditcancel: function(adding) {
|
||||
if (twitterConfigNodeIntervalId) {
|
||||
window.clearTimeout(twitterConfigNodeIntervalId);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="twitter in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-twitter"><i class="fa fa-user"></i> Log in as</label>
|
||||
<input type="text" id="node-input-twitter">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-user"><i class="fa fa-search"></i> Search</label>
|
||||
<select type="text" id="node-input-user" style="display: inline-block; vertical-align: middle; width:60%;">
|
||||
<option value="false">all public tweets</option>
|
||||
<option value="true">the tweets of who you follow</option>
|
||||
<option value="user">the tweets of specific users</option>
|
||||
<option value="dm">your direct messages</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-input-tags-row">
|
||||
<label for="node-input-tags"><i class="fa fa-tags"></i> <span id="node-input-tags-label">for</span></label>
|
||||
<input type="text" id="node-input-tags" placeholder="comma-separated words, @ids, #tags">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: Use commas without spaces between multiple search terms. Comma = OR, Space = AND.
|
||||
<br/>The Twitter API WILL NOT deliver 100% of all tweets.
|
||||
<br/>Tweets of who you follow will include their retweets and favourites.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="twitter in">
|
||||
<p>Twitter input node. Can be used to search either:
|
||||
<ul><li>the public or a user's stream for tweets containing the configured search term</li>
|
||||
<li>all tweets by specific users</li>
|
||||
<li>direct messages received by the authenticated user</li>
|
||||
</ul></p>
|
||||
<p>Use space for <i>and</i> and comma , for <i>or</i> when searching for multiple terms.</p>
|
||||
<p>Sets the <b>msg.topic</b> to <i>tweets/</i> and then appends the senders screen name.</p>
|
||||
<p>Sets <b>msg.location</b> to the tweeters location if known.</p>
|
||||
<p>Sets <b>msg.tweet</b> to the full tweet object as documented by <a href="https://dev.twitter.com/docs/platform-objects/tweets">Twitter</a>.
|
||||
<p><b>Note:</b> when set to a specific user's tweets, or your direct messages, the node is subject to
|
||||
Twitter's API rate limiting. If you deploy the flows multiple times within a 15 minute window, you may
|
||||
exceed the limit and will see errors from the node. These errors will clear when the current 15 minute window
|
||||
passes.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('twitter in',{
|
||||
category: 'social-input',
|
||||
color:"#C0DEED",
|
||||
defaults: {
|
||||
twitter: {type:"twitter-credentials",required:true},
|
||||
tags: {value:"",validate:function(v) { return this.user == "dm" || v.length > 0;}},
|
||||
user: {value:"false",required:true},
|
||||
name: {value:""},
|
||||
topic: {value:"tweets"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "twitter.png",
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.user == "dm") {
|
||||
var user = RED.nodes.node(this.twitter);
|
||||
return (user?user.label()+" ":"")+"DMs";
|
||||
} else if (this.user == "user") {
|
||||
return this.tags+" tweets";
|
||||
}
|
||||
return this.tags;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-user").change(function() {
|
||||
var type = $("#node-input-user option:selected").val();
|
||||
if (type == "user") {
|
||||
$("#node-input-tags-row").show();
|
||||
$("#node-input-tags-label").html("User");
|
||||
$("#node-input-tags").attr("placeholder","comma-separated @twitter handles");
|
||||
} else if (type == "dm") {
|
||||
$("#node-input-tags-row").hide();
|
||||
} else {
|
||||
$("#node-input-tags-row").show();
|
||||
$("#node-input-tags-label").html("for");
|
||||
$("#node-input-tags").attr("placeholder","comma-separated words, @ids, #hashtags");
|
||||
}
|
||||
|
||||
});
|
||||
$("#node-input-user").change();
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="twitter out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-twitter"><i class="fa fa-user"></i> Twitter</label>
|
||||
<input type="text" id="node-input-twitter">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="twitter out">
|
||||
<p>Twitter out node. Tweets the <b>msg.payload</b>.</p>
|
||||
<p>To send a Direct Message (DM) - use a payload like "D {username} {message}"</p>
|
||||
<p>If <b>msg.media</b> exists and is a Buffer object, this node will treat it
|
||||
as an image and attach it to the tweet.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('twitter out',{
|
||||
category: 'social-output',
|
||||
color:"#C0DEED",
|
||||
defaults: {
|
||||
twitter: {type:"twitter-credentials",required:true},
|
||||
name: {value:"Tweet"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "twitter.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,383 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var ntwitter = require('twitter-ng');
|
||||
var OAuth= require('oauth').OAuth;
|
||||
var request = require('request');
|
||||
|
||||
function TwitterNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.screen_name = n.screen_name;
|
||||
}
|
||||
RED.nodes.registerType("twitter-credentials",TwitterNode,{
|
||||
credentials: {
|
||||
screen_name: {type:"text"},
|
||||
access_token: {type: "password"},
|
||||
access_token_secret: {type:"password"}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Populate msg.location based on data found in msg.tweet.
|
||||
*/
|
||||
function addLocationToTweet(msg) {
|
||||
if(msg.tweet) {
|
||||
if(msg.tweet.geo) { // if geo is set, always set location from geo
|
||||
if(msg.tweet.geo.coordinates && msg.tweet.geo.coordinates.length === 2) {
|
||||
if (!msg.location) { msg.location = {}; }
|
||||
// coordinates[0] is lat, coordinates[1] is lon
|
||||
msg.location.lat = msg.tweet.geo.coordinates[0];
|
||||
msg.location.lon = msg.tweet.geo.coordinates[1];
|
||||
msg.location.icon = "twitter";
|
||||
}
|
||||
} else if(msg.tweet.coordinates) { // otherwise attempt go get it from coordinates
|
||||
if(msg.tweet.coordinates.coordinates && msg.tweet.coordinates.coordinates.length === 2) {
|
||||
if (!msg.location) { msg.location = {}; }
|
||||
// WARNING! coordinates[1] is lat, coordinates[0] is lon!!!
|
||||
msg.location.lat = msg.tweet.coordinates.coordinates[1];
|
||||
msg.location.lon = msg.tweet.coordinates.coordinates[0];
|
||||
msg.location.icon = "twitter";
|
||||
}
|
||||
} // if none of these found then just do nothing
|
||||
} // if no msg.tweet then just do nothing
|
||||
}
|
||||
|
||||
function TwitterInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.active = true;
|
||||
this.user = n.user;
|
||||
//this.tags = n.tags.replace(/ /g,'');
|
||||
this.tags = n.tags;
|
||||
this.twitter = n.twitter;
|
||||
this.topic = n.topic||"tweets";
|
||||
this.twitterConfig = RED.nodes.getNode(this.twitter);
|
||||
var credentials = RED.nodes.getCredentials(this.twitter);
|
||||
|
||||
if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
|
||||
var twit = new ntwitter({
|
||||
consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
|
||||
consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
|
||||
access_token_key: credentials.access_token,
|
||||
access_token_secret: credentials.access_token_secret
|
||||
});
|
||||
|
||||
//setInterval(function() {
|
||||
// twit.get("/application/rate_limit_status.json",null,function(err,cb) {
|
||||
// console.log("direct_messages:",cb["resources"]["direct_messages"]);
|
||||
// });
|
||||
//
|
||||
//},10000);
|
||||
|
||||
var node = this;
|
||||
if (this.user === "user") {
|
||||
node.poll_ids = [];
|
||||
node.since_ids = {};
|
||||
var users = node.tags.split(",");
|
||||
for (var i=0;i<users.length;i++) {
|
||||
var user = users[i].replace(" ","");
|
||||
twit.getUserTimeline({
|
||||
screen_name:user,
|
||||
trim_user:0,
|
||||
count:1
|
||||
},function() {
|
||||
var u = user+"";
|
||||
return function(err,cb) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
return;
|
||||
}
|
||||
if (cb[0]) {
|
||||
node.since_ids[u] = cb[0].id_str;
|
||||
} else {
|
||||
node.since_ids[u] = '0';
|
||||
}
|
||||
node.poll_ids.push(setInterval(function() {
|
||||
twit.getUserTimeline({
|
||||
screen_name:u,
|
||||
trim_user:0,
|
||||
since_id:node.since_ids[u]
|
||||
},function(err,cb) {
|
||||
if (cb) {
|
||||
for (var t=cb.length-1;t>=0;t-=1) {
|
||||
var tweet = cb[t];
|
||||
var where = tweet.user.location;
|
||||
var la = tweet.lang || tweet.user.lang;
|
||||
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, lang:la, tweet:tweet };
|
||||
if (where) {
|
||||
msg.location = {place:where};
|
||||
addLocationToTweet(msg);
|
||||
}
|
||||
node.send(msg);
|
||||
if (t == 0) {
|
||||
node.since_ids[u] = tweet.id_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
},60000));
|
||||
}
|
||||
}());
|
||||
}
|
||||
} else if (this.user === "dm") {
|
||||
node.poll_ids = [];
|
||||
twit.getDirectMessages({
|
||||
screen_name:node.twitterConfig.screen_name,
|
||||
trim_user:0,
|
||||
count:1
|
||||
},function(err,cb) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
return;
|
||||
}
|
||||
if (cb[0]) {
|
||||
node.since_id = cb[0].id_str;
|
||||
} else {
|
||||
node.since_id = '0';
|
||||
}
|
||||
node.poll_ids.push(setInterval(function() {
|
||||
twit.getDirectMessages({
|
||||
screen_name:node.twitterConfig.screen_name,
|
||||
trim_user:0,
|
||||
since_id:node.since_id
|
||||
},function(err,cb) {
|
||||
if (cb) {
|
||||
for (var t=cb.length-1;t>=0;t-=1) {
|
||||
var tweet = cb[t];
|
||||
var where = tweet.sender.location;
|
||||
var la = tweet.lang || tweet.sender.lang;
|
||||
var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, lang:la, tweet:tweet };
|
||||
if (where) {
|
||||
msg.location = {place:where};
|
||||
addLocationToTweet(msg);
|
||||
}
|
||||
node.send(msg);
|
||||
if (t == 0) {
|
||||
node.since_id = tweet.id_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
},120000));
|
||||
});
|
||||
|
||||
} else if (this.tags !== "") {
|
||||
try {
|
||||
var thing = 'statuses/filter';
|
||||
if (this.user === "true") { thing = 'user'; }
|
||||
var st = { track: [node.tags] };
|
||||
var bits = node.tags.split(",");
|
||||
if (bits.length == 4) {
|
||||
if ((Number(bits[0]) < Number(bits[2])) && (Number(bits[1]) < Number(bits[3]))) {
|
||||
st = { locations: node.tags };
|
||||
}
|
||||
else {
|
||||
node.log("possible bad geo area format. Should be lower-left lon, lat, upper-right lon, lat");
|
||||
}
|
||||
}
|
||||
|
||||
var setupStream = function() {
|
||||
if (node.active) {
|
||||
twit.stream(thing, st, function(stream) {
|
||||
//console.log(st);
|
||||
//twit.stream('user', { track: [node.tags] }, function(stream) {
|
||||
//twit.stream('site', { track: [node.tags] }, function(stream) {
|
||||
//twit.stream('statuses/filter', { track: [node.tags] }, function(stream) {
|
||||
node.stream = stream;
|
||||
stream.on('data', function(tweet) {
|
||||
if (tweet.user !== undefined) {
|
||||
var where = tweet.user.location;
|
||||
var la = tweet.lang || tweet.user.lang;
|
||||
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, lang:la, tweet:tweet };
|
||||
if (where) {
|
||||
msg.location = {place:where};
|
||||
addLocationToTweet(msg);
|
||||
}
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
stream.on('limit', function(tweet) {
|
||||
node.log("tweet rate limit hit");
|
||||
});
|
||||
stream.on('error', function(tweet,rc) {
|
||||
if (rc == 420) {
|
||||
node.warn("Twitter rate limit hit");
|
||||
} else {
|
||||
node.warn("Stream error:"+tweet.toString()+" ("+rc+")");
|
||||
}
|
||||
setTimeout(setupStream,10000);
|
||||
});
|
||||
stream.on('destroy', function (response) {
|
||||
if (this.active) {
|
||||
node.warn("twitter ended unexpectedly");
|
||||
setTimeout(setupStream,10000);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
setupStream();
|
||||
}
|
||||
catch (err) {
|
||||
node.error(err);
|
||||
}
|
||||
} else {
|
||||
this.error("Invalid tag property");
|
||||
}
|
||||
} else {
|
||||
this.error("missing twitter credentials");
|
||||
}
|
||||
|
||||
this.on('close', function() {
|
||||
if (this.stream) {
|
||||
this.active = false;
|
||||
this.stream.destroy();
|
||||
}
|
||||
if (this.poll_ids) {
|
||||
for (var i=0;i<this.poll_ids.length;i++) {
|
||||
clearInterval(this.poll_ids[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("twitter in",TwitterInNode);
|
||||
|
||||
|
||||
function TwitterOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.topic = n.topic;
|
||||
this.twitter = n.twitter;
|
||||
this.twitterConfig = RED.nodes.getNode(this.twitter);
|
||||
var credentials = RED.nodes.getCredentials(this.twitter);
|
||||
var node = this;
|
||||
|
||||
if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
|
||||
var twit = new ntwitter({
|
||||
consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
|
||||
consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
|
||||
access_token_key: credentials.access_token,
|
||||
access_token_secret: credentials.access_token_secret
|
||||
});
|
||||
node.on("input", function(msg) {
|
||||
node.status({fill:"blue",shape:"dot",text:"tweeting"});
|
||||
|
||||
if (msg.payload.length > 140) {
|
||||
msg.payload = msg.payload.slice(0,139);
|
||||
node.warn("Tweet greater than 140 : truncated");
|
||||
}
|
||||
|
||||
if (msg.media && Buffer.isBuffer(msg.media)) {
|
||||
var apiUrl = "https://api.twitter.com/1.1/statuses/update_with_media.json";
|
||||
var signedUrl = oa.signUrl(apiUrl,
|
||||
credentials.access_token,
|
||||
credentials.access_token_secret,
|
||||
"POST");
|
||||
|
||||
var r = request.post(signedUrl,function(err,httpResponse,body) {
|
||||
if (err) {
|
||||
node.error(err.toString());
|
||||
node.status({fill:"red",shape:"ring",text:"failed"});
|
||||
} else {
|
||||
var response = JSON.parse(body);
|
||||
if (response.errors) {
|
||||
var errorList = response.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
|
||||
node.error("tweet failed: "+errorList);
|
||||
node.status({fill:"red",shape:"ring",text:"failed"});
|
||||
} else {
|
||||
node.status({});
|
||||
}
|
||||
}
|
||||
});
|
||||
var form = r.form();
|
||||
form.append("status",msg.payload);
|
||||
form.append("media[]",msg.media,{filename:"image"});
|
||||
|
||||
} else {
|
||||
twit.updateStatus(msg.payload, function (err, data) {
|
||||
if (err) {
|
||||
node.status({fill:"red",shape:"ring",text:"failed"});
|
||||
node.error(err);
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("twitter out",TwitterOutNode);
|
||||
|
||||
var oa = new OAuth(
|
||||
"https://api.twitter.com/oauth/request_token",
|
||||
"https://api.twitter.com/oauth/access_token",
|
||||
"OKjYEd1ef2bfFolV25G5nQ",
|
||||
"meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
|
||||
"1.0",
|
||||
null,
|
||||
"HMAC-SHA1"
|
||||
);
|
||||
|
||||
RED.httpAdmin.get('/twitter-credentials/:id/auth', function(req, res){
|
||||
var credentials = {};
|
||||
oa.getOAuthRequestToken({
|
||||
oauth_callback: req.query.callback
|
||||
},function(error, oauth_token, oauth_token_secret, results){
|
||||
if (error) {
|
||||
var resp = '<h2>Oh no!</h2>'+
|
||||
'<p>Something went wrong with the authentication process. The following error was returned:<p>'+
|
||||
'<p><b>'+error.statusCode+'</b>: '+error.data+'</p>'+
|
||||
'<p>One known cause of this type of failure is if the clock is wrong on system running Node-RED.';
|
||||
res.send(resp)
|
||||
} else {
|
||||
credentials.oauth_token = oauth_token;
|
||||
credentials.oauth_token_secret = oauth_token_secret;
|
||||
res.redirect('https://twitter.com/oauth/authorize?oauth_token='+oauth_token)
|
||||
RED.nodes.addCredentials(req.params.id,credentials);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
RED.httpAdmin.get('/twitter-credentials/:id/auth/callback', function(req, res, next){
|
||||
var credentials = RED.nodes.getCredentials(req.params.id);
|
||||
credentials.oauth_verifier = req.query.oauth_verifier;
|
||||
|
||||
oa.getOAuthAccessToken(
|
||||
credentials.oauth_token,
|
||||
credentials.token_secret,
|
||||
credentials.oauth_verifier,
|
||||
function(error, oauth_access_token, oauth_access_token_secret, results){
|
||||
if (error){
|
||||
console.log(error);
|
||||
res.send("yeah something broke.");
|
||||
} else {
|
||||
credentials = {};
|
||||
credentials.access_token = oauth_access_token;
|
||||
credentials.access_token_secret = oauth_access_token_secret;
|
||||
credentials.screen_name = "@"+results.screen_name;
|
||||
RED.nodes.addCredentials(req.params.id,credentials);
|
||||
res.send("<html><head></head><body>Authorised - you can close this window and return to Node-RED</body></html>");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="feedparse">
|
||||
<div class="form-row">
|
||||
<label for="node-input-url"><i class="fa fa-globe"></i> Feed url</label>
|
||||
<input type="text" id="node-input-url">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-interval"><i class="fa fa-repeat"></i> Repeat <span style="font-size: 0.9em;">(M)</span></label>
|
||||
<input type="text" id="node-input-interval" placeholder="minutes">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<!-- <div class="form-tips"></div> -->
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="feedparse">
|
||||
<p>Monitors an RSS/atom feed for new entries.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('feedparse',{
|
||||
category: 'advanced-input',
|
||||
color:"#C0DEED",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
url: {value:"", required:true},
|
||||
interval: { value:15, required: true,validate:RED.validators.number()}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "feed.png",
|
||||
label: function() {
|
||||
return this.name||this.url;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var FeedParser = require("feedparser");
|
||||
var request = require("request");
|
||||
|
||||
function FeedParseNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.url = n.url;
|
||||
this.interval = (parseInt(n.interval)||15) * 60000;
|
||||
var node = this;
|
||||
this.interval_id = null;
|
||||
this.seen = {};
|
||||
if (this.url !== "") {
|
||||
var getFeed = function() {
|
||||
var req = request(node.url, {timeout: 10000, pool: false});
|
||||
//req.setMaxListeners(50);
|
||||
//req.setHeader('user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36');
|
||||
req.setHeader('accept', 'text/html,application/xhtml+xml');
|
||||
|
||||
var feedparser = new FeedParser();
|
||||
|
||||
req.on('error', function(err) { node.error(err); });
|
||||
|
||||
req.on('response', function(res) {
|
||||
if (res.statusCode != 200) { node.warn('error - Bad status code'); }
|
||||
else { res.pipe(feedparser); }
|
||||
});
|
||||
|
||||
feedparser.on('error', function(error) { node.error(error); });
|
||||
|
||||
feedparser.on('readable', function () {
|
||||
var stream = this, article;
|
||||
while (article = stream.read()) {
|
||||
if (!(article.guid in node.seen) || ( node.seen[article.guid] !== 0 && node.seen[article.guid] != article.date.getTime())) {
|
||||
node.seen[article.guid] = article.date?article.date.getTime():0;
|
||||
var msg = {
|
||||
topic: article.origlink || article.link,
|
||||
payload: article.description,
|
||||
article: article
|
||||
};
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
feedparser.on('meta', function (meta) {});
|
||||
feedparser.on('end', function () {});
|
||||
};
|
||||
this.interval_id = setInterval(function() { getFeed(); }, node.interval);
|
||||
getFeed();
|
||||
} else {
|
||||
this.error("Invalid url");
|
||||
}
|
||||
|
||||
this.on("close", function() {
|
||||
if (this.interval_id != null) {
|
||||
clearInterval(this.interval_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("feedparse",FeedParseNode);
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="e-mail">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-envelope"></i> To</label>
|
||||
<input type="text" id="node-input-name" placeholder="email@address.com">
|
||||
</div>
|
||||
<!-- <div class="form-row">
|
||||
<label for="node-input-pin"><i class="fa fa-asterisk"></i> Service</label>
|
||||
<select type="text" id="node-input-pin" style="width: 150px;">
|
||||
<option value="-" disabled> </option>
|
||||
<option value="DynectEmail">DynectEmail</option>
|
||||
<option value="Gmail">Gmail</option>
|
||||
<option value="hot.ee">hot.ee</option>
|
||||
<option value="Hotmail">Hotmail</option>
|
||||
<option value="iCloud">iCloud</option>
|
||||
<option value="mail.ee">mail.ee</option>
|
||||
<option value="Mail.Ru">Mail.Ru</option>
|
||||
<option value="Mailgun">Mailgun</option>
|
||||
<option value="Mailjet">Mailjet</option>
|
||||
<option value="Mandrill">Mandrill</option>
|
||||
<option value="Postmark">Postmark</option>
|
||||
<option value="QQ">QQ</option>
|
||||
<option value="QQex">QQex</option>
|
||||
<option value="SendGrid">SendGrid</option>
|
||||
<option value="SendCloud">SendCloud</option>
|
||||
<option value="SES">SES</option>
|
||||
<option value="Yahoo">Yahoo</option>
|
||||
<option value="yandex">yandex</option>
|
||||
<option value="Zoho">Zoho</option>
|
||||
</select>
|
||||
</div> -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
||||
<input type="text" id="node-input-server" placeholder="smtp.gmail.com">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-random"></i> Port</label>
|
||||
<input type="text" id="node-input-port" placeholder="465">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-userid"><i class="fa fa-user"></i> Userid</label>
|
||||
<input type="text" id="node-input-userid">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
|
||||
<input type="password" id="node-input-password">
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-dname"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-dname" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips" id="node-tip"><b>Note:</b> Copied credentials from global emailkeys.js file.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="e-mail">
|
||||
<p>Sends the <b>msg.payload</b> as an email, with a subject of <b>msg.topic</b>.</p>
|
||||
<p>The default message recipient can be configured in the node, if it is left
|
||||
blank it should be set using the <b>msg.to</b> property of the incoming message.</p>
|
||||
<p>The payload can be html format.</p>
|
||||
<p>If the payload is a binary buffer then it will be converted to an attachment.
|
||||
The filename should be set using <b>msg.filename</b>. Optionally <b>msg.description</b> can be added for the body text.</p>
|
||||
<p>Alternatively you may provide <b>msg.attachments</b> which should contain an array of one or
|
||||
more attachments in <a href="https://www.npmjs.com/package/nodemailer#attachments" target="_new">nodemailer</a> format.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
RED.nodes.registerType('e-mail',{
|
||||
category: 'social-output',
|
||||
color:"#c7e9c0",
|
||||
defaults: {
|
||||
server: {value:"smtp.gmail.com",required:true},
|
||||
port: {value:"465",required:true},
|
||||
name: {value:"",required:true},
|
||||
dname: {value:""}
|
||||
},
|
||||
credentials: {
|
||||
userid: {type:"text"},
|
||||
password: {type: "password"},
|
||||
global: { type:"boolean"}
|
||||
},
|
||||
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "envelope.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.dname||this.name||"email";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return (this.dname||!this.topic)?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.credentials.global) {
|
||||
$('#node-tip').show();
|
||||
} else {
|
||||
$('#node-tip').hide();
|
||||
};
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="e-mail in">
|
||||
<div class="form-row node-input-repeat">
|
||||
<label for="node-input-repeat"><i class="fa fa-repeat"></i> Check Repeat (S)</label>
|
||||
<input type="text" id="node-input-repeat" placeholder="300">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
||||
<input type="text" id="node-input-server" placeholder="imap.gmail.com">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-random"></i> Port</label>
|
||||
<input type="text" id="node-input-port" placeholder="993">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-userid"><i class="fa fa-user"></i> Userid</label>
|
||||
<input type="text" id="node-input-userid">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
|
||||
<input type="password" id="node-input-password">
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips" id="node-tip"><b>Note:</b> Copied credentials from global emailkeys.js file.</div>
|
||||
<div id="node-input-tip" class="form-tips">Tip: <b>ONLY</b> retrieves the single most recent email.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="e-mail in">
|
||||
<p>Repeatedly gets a <b>single email</b> from an IMAP server and forwards on as a msg if not already seen.</p>
|
||||
<p>The subject is loaded into <b>msg.topic</b> and <b>msg.payload</b> is the plain text body.
|
||||
If there is text/html then that is returned in <b>msg.html</b>. <b>msg.from</b> and <b>msg.date</b> are also set if you need them.</p>
|
||||
<p>Uses the imap module.</p>
|
||||
<p><b>Note:</b> this node <i>only</i> gets the most recent single email from the inbox, so set the repeat (polling) time appropriately.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
RED.nodes.registerType('e-mail in',{
|
||||
category: 'social-input',
|
||||
color:"#c7e9c0",
|
||||
defaults: {
|
||||
repeat: {value:"300",required:true},
|
||||
server: {value:"imap.gmail.com",required:true},
|
||||
port: {value:"993",required:true},
|
||||
name: {value:""}
|
||||
},
|
||||
credentials: {
|
||||
userid: {type:"text"},
|
||||
password: {type: "password"},
|
||||
global: { type:"boolean"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "envelope.png",
|
||||
label: function() {
|
||||
return this.name||"email";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return (this.name||!this.topic)?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.credentials.global) {
|
||||
$('#node-tip').show();
|
||||
} else {
|
||||
$('#node-tip').hide();
|
||||
};
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -1,260 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var nodemailer = require("nodemailer");
|
||||
var Imap = require('imap');
|
||||
|
||||
//console.log(nodemailer.Transport.transports.SMTP.wellKnownHosts);
|
||||
|
||||
try {
|
||||
var globalkeys = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
|
||||
} catch(err) {
|
||||
}
|
||||
|
||||
function EmailNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.topic = n.topic;
|
||||
this.name = n.name;
|
||||
this.outserver = n.server;
|
||||
this.outport = n.port;
|
||||
var flag = false;
|
||||
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
|
||||
this.userid = this.credentials.userid;
|
||||
} else {
|
||||
if (globalkeys) {
|
||||
this.userid = globalkeys.user;
|
||||
flag = true;
|
||||
} else {
|
||||
this.error("No e-mail userid set");
|
||||
}
|
||||
}
|
||||
if (this.credentials && this.credentials.hasOwnProperty("password")) {
|
||||
this.password = this.credentials.password;
|
||||
} else {
|
||||
if (globalkeys) {
|
||||
this.password = globalkeys.pass;
|
||||
flag = true;
|
||||
} else {
|
||||
this.error("No e-mail password set");
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
|
||||
}
|
||||
var node = this;
|
||||
|
||||
var smtpTransport = nodemailer.createTransport({
|
||||
host: node.outserver,
|
||||
port: node.outport,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: node.userid,
|
||||
pass: node.password
|
||||
}
|
||||
});
|
||||
|
||||
this.on("input", function(msg) {
|
||||
if (smtpTransport) {
|
||||
node.status({fill:"blue",shape:"dot",text:"sending"});
|
||||
if (msg.to && node.name && (msg.to !== node.name)) {
|
||||
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
var sendopts = { from: node.userid }; // sender address
|
||||
sendopts.to = msg.to || node.name; // comma separated list of addressees
|
||||
sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; // subject line
|
||||
if (Buffer.isBuffer(msg.payload)) { // if it's a buffer in the payload then auto create an attachment instead
|
||||
sendopts.attachments = [ { content: msg.payload, filename:(msg.filename.replace(/^.*[\\\/]/, '') || "file.bin") } ];
|
||||
if (msg.hasOwnProperty("headers") && msg.headers.hasOwnProperty("content-type")) {
|
||||
sendopts.attachments[0].contentType = msg.headers["content-type"];
|
||||
}
|
||||
// Create some body text..
|
||||
sendopts.text = "Your file from Node-RED is attached : "+(msg.filename.replace(/^.*[\\\/]/, '') || "file.bin")+ (msg.hasOwnProperty("description") ? "\n\n"+msg.description : "");
|
||||
}
|
||||
else {
|
||||
var payload = RED.util.ensureString(msg.payload);
|
||||
sendopts.text = payload; // plaintext body
|
||||
if (/<[a-z][\s\S]*>/i.test(payload)) { sendopts.html = payload; } // html body
|
||||
if (msg.attachments) { sendopts.attachments = msg.attachments; } // add attachments
|
||||
}
|
||||
smtpTransport.sendMail(sendopts, function(error, info) {
|
||||
if (error) {
|
||||
node.error(error);
|
||||
node.status({fill:"red",shape:"ring",text:"send failed"});
|
||||
} else {
|
||||
node.log("Message sent: " + info.response);
|
||||
node.status({});
|
||||
}
|
||||
});
|
||||
}
|
||||
else { node.warn("No Email credentials found. See info panel."); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("e-mail",EmailNode,{
|
||||
credentials: {
|
||||
userid: {type:"text"},
|
||||
password: {type: "password"},
|
||||
global: { type:"boolean"}
|
||||
}
|
||||
});
|
||||
|
||||
function EmailInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.name = n.name;
|
||||
this.repeat = n.repeat * 1000 || 300000;
|
||||
this.inserver = n.server || globalkeys.server || "imap.gmail.com";
|
||||
this.inport = n.port || globalkeys.port || "993";
|
||||
var flag = false;
|
||||
|
||||
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
|
||||
this.userid = this.credentials.userid;
|
||||
} else {
|
||||
if (globalkeys) {
|
||||
this.userid = globalkeys.user;
|
||||
flag = true;
|
||||
} else {
|
||||
this.error("No e-mail userid set");
|
||||
}
|
||||
}
|
||||
if (this.credentials && this.credentials.hasOwnProperty("password")) {
|
||||
this.password = this.credentials.password;
|
||||
} else {
|
||||
if (globalkeys) {
|
||||
this.password = globalkeys.pass;
|
||||
flag = true;
|
||||
} else {
|
||||
this.error("No e-mail password set");
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
|
||||
}
|
||||
|
||||
var node = this;
|
||||
this.interval_id = null;
|
||||
var oldmail = {};
|
||||
|
||||
var imap = new Imap({
|
||||
user: node.userid,
|
||||
password: node.password,
|
||||
host: node.inserver,
|
||||
port: node.inport,
|
||||
tls: true,
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
connTimeout: node.repeat,
|
||||
authTimeout: node.repeat
|
||||
});
|
||||
|
||||
if (!isNaN(this.repeat) && this.repeat > 0) {
|
||||
node.log("repeat = "+this.repeat);
|
||||
this.interval_id = setInterval( function() {
|
||||
node.emit("input",{});
|
||||
}, this.repeat );
|
||||
}
|
||||
|
||||
this.on("input", function(msg) {
|
||||
imap.once('ready', function() {
|
||||
node.status({fill:"blue",shape:"dot",text:"fetching"});
|
||||
var pay = {};
|
||||
imap.openBox('INBOX', true, function(err, box) {
|
||||
if (box.messages.total > 0) {
|
||||
var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] });
|
||||
f.on('message', function(msg, seqno) {
|
||||
node.log('message: #'+ seqno);
|
||||
var prefix = '(#' + seqno + ') ';
|
||||
msg.on('body', function(stream, info) {
|
||||
var buffer = '';
|
||||
stream.on('data', function(chunk) {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
stream.on('end', function() {
|
||||
if (info.which !== 'TEXT') {
|
||||
pay.from = Imap.parseHeader(buffer).from[0];
|
||||
pay.topic = Imap.parseHeader(buffer).subject[0];
|
||||
pay.date = Imap.parseHeader(buffer).date[0];
|
||||
} else {
|
||||
var parts = buffer.split("Content-Type");
|
||||
for (var p = 0; p < parts.length; p++) {
|
||||
if (parts[p].indexOf("text/plain") >= 0) {
|
||||
pay.payload = parts[p].split("\n").slice(1,-2).join("\n").trim();
|
||||
}
|
||||
if (parts[p].indexOf("text/html") >= 0) {
|
||||
pay.html = parts[p].split("\n").slice(1,-2).join("\n").trim();
|
||||
}
|
||||
}
|
||||
//pay.body = buffer;
|
||||
}
|
||||
});
|
||||
});
|
||||
msg.on('end', function() {
|
||||
//node.log('Finished: '+prefix);
|
||||
});
|
||||
});
|
||||
f.on('error', function(err) {
|
||||
node.warn('fetch error: ' + err);
|
||||
node.status({fill:"red",shape:"ring",text:"fetch error"});
|
||||
});
|
||||
f.on('end', function() {
|
||||
if (JSON.stringify(pay) !== oldmail) {
|
||||
node.send(pay);
|
||||
oldmail = JSON.stringify(pay);
|
||||
node.log('received new email: '+pay.topic);
|
||||
}
|
||||
else { node.log('duplicate not sent: '+pay.topic); }
|
||||
//node.status({fill:"green",shape:"dot",text:"ok"});
|
||||
node.status({});
|
||||
imap.end();
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.log("you have achieved inbox zero");
|
||||
//node.status({fill:"green",shape:"dot",text:"ok"});
|
||||
node.status({});
|
||||
imap.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
node.status({fill:"grey",shape:"dot",text:"connecting"});
|
||||
imap.connect();
|
||||
});
|
||||
|
||||
imap.on('error', function(err) {
|
||||
node.log(err);
|
||||
node.status({fill:"red",shape:"ring",text:"connect error"});
|
||||
});
|
||||
|
||||
this.on("error", function(err) {
|
||||
node.log("error: ",err);
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
if (this.interval_id != null) {
|
||||
clearInterval(this.interval_id);
|
||||
}
|
||||
if (imap) { imap.destroy(); }
|
||||
});
|
||||
|
||||
node.emit("input",{});
|
||||
}
|
||||
RED.nodes.registerType("e-mail in",EmailInNode,{
|
||||
credentials: {
|
||||
userid: {type:"text"},
|
||||
password: {type: "password"},
|
||||
global: { type:"boolean"}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,206 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="irc in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-ircserver"><i class="fa fa-globe"></i> IRC Server</label>
|
||||
<input type="text" id="node-input-ircserver">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-channel"><i class="fa fa-random"></i> Channel</label>
|
||||
<input type="text" id="node-input-channel" placeholder="#nodered">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">The channel to join must start with a # (as per normal irc rules...)<br/>
|
||||
You may join multiple channels by comma separating a list - #chan1,#chan2,etc.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="irc in">
|
||||
<p>Connects to a channel on an IRC server.</p>
|
||||
<p>You may join multiple channels by comma separating a list - #chan1,#chan2,#etc.</p>
|
||||
<p>Any messages on that channel will appear on the <code>msg.payload</code> at the output,
|
||||
while <code>msg.topic</code> will contain who it is from.
|
||||
<code>msg.to</code> contains either the name of the channel or PRIV in the case of a pm.</p>
|
||||
<p>The second output provides a <code>msg.payload</code> that has any status messages such as joins, parts, kicks etc.</p>
|
||||
<p>The type of the status message is set as <code>msg.payload.type</code>.</p>
|
||||
<p>The possible status types are: <br />
|
||||
<table border="1" cellpadding="1" cellspacing="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>message</td>
|
||||
<td>message is sent into the channel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pm</td>
|
||||
<td>private message to the bot</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>join</td>
|
||||
<td>a user joined the channel (also triggered when the bot joins a channel)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>invite</td>
|
||||
<td>the bot is being invited to a channel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>part</td>
|
||||
<td>a user leaves a channel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>quit</td>
|
||||
<td>a user quits a channel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>kick</td>
|
||||
<td>a user is kicked from a channel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>names</td>
|
||||
<td>retrieves the list of users when the bot joins a channel</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('irc in',{
|
||||
category: 'social-input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
ircserver: {type:"irc-server", required:true},
|
||||
channel: {value:"",required:true,validate:RED.validators.regex(/^#/)}
|
||||
},
|
||||
color:"Silver",
|
||||
inputs:0,
|
||||
outputs:2,
|
||||
icon: "hash.png",
|
||||
label: function() {
|
||||
var ircNode = RED.nodes.node(this.ircserver);
|
||||
return this.name || (ircNode ? ircNode.label() : "irc");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if ((this.ircserver !== undefined) && (this.ircserver !== "")) {
|
||||
this.channel = this.channel || RED.nodes.node(this.ircserver).channel;
|
||||
$("#node-input-channel").val(this.channel);
|
||||
}
|
||||
else { this.channel = this.channel; }
|
||||
$("#node-input-channel").val(this.channel);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="irc out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-ircserver"><i class="fa fa-globe"></i> IRC Server</label>
|
||||
<input type="text" id="node-input-ircserver">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-channel"><i class="fa fa-random"></i> Channel</label>
|
||||
<input type="text" id="node-input-channel" placeholder="#nodered">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-sendObject"><i class="fa fa-arrows"></i> Action</label>
|
||||
<select type="text" id="node-input-sendObject" style="display: inline-block; vertical-align: middle; width:70%;">
|
||||
<option value="pay">Send payload to channel(s)</option>
|
||||
<option value="true">Use msg.topic to set nickname or channel(s)</option>
|
||||
<option value="false">Send complete msg object to channel(s)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">The channel to join must start with a # (as per normal irc rules...)<br/>
|
||||
Sending the complete object will stringify the whole msg object before sending.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="irc out">
|
||||
<p>Sends messages to a channel on an IRC server</p>
|
||||
<p>You can send just the <code>msg.payload</code>, or the complete <code>msg</code> object to the selected channel,
|
||||
or you can select to use <code>msg.topic</code> to send the <code>msg.payload</code> to a specific user (private message) or channel.</p>
|
||||
<p>If multiple output channels are listed (eg. #chan1,#chan2), then the message will be sent to all of them.</p>
|
||||
<p><b>Note:</b> you can only send to channels you have previously joined so they MUST be specified in the node - even if you then decide to use a subset in msg.topic</p>
|
||||
<p>You may send RAW commands using <code>msg.raw</code> - This must contain an array of parameters - eg. <pre>["privmsg","#nodered","Hello world"]</pre></p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('irc out',{
|
||||
category: 'social-output',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
sendObject: {value:"pay", required:true},
|
||||
ircserver: {type:"irc-server", required:true},
|
||||
channel: {value:"",required:true,validate:RED.validators.regex(/^#/)}
|
||||
},
|
||||
color:"Silver",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "hash.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name || (this.ircserver ? RED.nodes.node(this.ircserver).label() : "irc");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if ((this.ircserver !== undefined) && (this.ircserver !== "")) {
|
||||
this.channel = this.channel || RED.nodes.node(this.ircserver).channel;
|
||||
$("#node-input-channel").val(this.channel);
|
||||
}
|
||||
else { this.channel = this.channel; }
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="irc-server">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-server"><i class="fa fa-globe"></i> IRC Server</label>
|
||||
<input type="text" id="node-config-input-server" placeholder="irc.freenode.net">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-nickname"><i class="fa fa-user"></i> Nickname</label>
|
||||
<input type="text" id="node-config-input-nickname" placeholder="joe123">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('irc-server',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
server: {value:"",required:true},
|
||||
nickname: {value:"",required:true}
|
||||
},
|
||||
label: function() {
|
||||
return this.server;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,277 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var irc = require("irc");
|
||||
|
||||
// The Server Definition - this opens (and closes) the connection
|
||||
function IRCServerNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = n.server;
|
||||
this.channel = n.channel;
|
||||
this.nickname = n.nickname;
|
||||
this.lastseen = 0;
|
||||
this.ircclient = null;
|
||||
this.on("close", function() {
|
||||
if (this.ircclient != null) {
|
||||
this.ircclient.removeAllListeners();
|
||||
this.ircclient.disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("irc-server",IRCServerNode);
|
||||
|
||||
|
||||
// The Input Node
|
||||
function IrcInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.ircserver = n.ircserver;
|
||||
this.serverConfig = RED.nodes.getNode(this.ircserver);
|
||||
this.channel = n.channel || this.serverConfig.channel;
|
||||
var node = this;
|
||||
if (node.serverConfig.ircclient === null) {
|
||||
node.log("CONNECT: "+node.serverConfig.server);
|
||||
node.status({fill:"grey",shape:"dot",text:"connecting"});
|
||||
node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:true,autoRejoin:false,floodProtection:true,retryDelay:20000});
|
||||
node.serverConfig.ircclient.setMaxListeners(0);
|
||||
node.serverConfig.ircclient.addListener('error', function(message) {
|
||||
if (RED.settings.verbose) { node.log("ERR: "+JSON.stringify(message)); }
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('netError', function(message) {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
if (RED.settings.verbose) { node.log("NET: "+JSON.stringify(message)); }
|
||||
node.status({fill:"red",shape:"ring",text:"net error"});
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('connect', function() {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
if (RED.settings.verbose) { node.log("CONNECTED "); }
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('registered', function(message) {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
node.log(node.serverConfig.ircclient.nick+" ONLINE: "+message.server);
|
||||
node.status({fill:"yellow",shape:"dot",text:"connected"});
|
||||
node.serverConfig.ircclient.join( node.channel, function(data) {
|
||||
node.log(data+" JOINED: "+node.channel);
|
||||
node.status({fill:"green",shape:"dot",text:"joined"});
|
||||
});
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('ping', function(server) {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
if (RED.settings.verbose) { node.log("PING from "+JSON.stringify(server)); }
|
||||
node.status({fill:"green",shape:"dot",text:"ok"});
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('quit', function(nick, reason, channels, message) {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
if (RED.settings.verbose) { node.log("QUIT: "+nick+" "+reason+" "+channels+" "+JSON.stringify(message)); }
|
||||
node.status({fill:"grey",shape:"ring",text:"quit"});
|
||||
//node.serverConfig.ircclient.disconnect( function() {
|
||||
// node.serverConfig.ircclient.connect();
|
||||
//});
|
||||
//if (RED.settings.verbose) { node.log("restart"); } // then retry
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('raw', function (message) { // any message received means we are alive
|
||||
//console.log("RAW:"+JSON.stringify(message));
|
||||
if (message.commandType === "reply") {
|
||||
//console.log("RAW:"+JSON.stringify(message));
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
}
|
||||
});
|
||||
node.recon = setInterval( function() {
|
||||
//console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000);
|
||||
if ((Date.now()-node.serverConfig.lastseen) > 240000) { // if more than 4 mins since last seen
|
||||
node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
|
||||
}
|
||||
if ((Date.now()-node.serverConfig.lastseen) > 300000) { // If more than 5 mins
|
||||
//node.serverConfig.ircclient.disconnect();
|
||||
//node.serverConfig.ircclient.connect();
|
||||
node.status({fill:"grey",shape:"ring",text:"no connection"});
|
||||
if (RED.settings.verbose) { node.log("CONNECTION LOST ?"); }
|
||||
}
|
||||
//node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
|
||||
}, 60000); // check every 1 min
|
||||
//node.serverConfig.ircclient.connect();
|
||||
}
|
||||
else { node.status({text:""}); }
|
||||
node.ircclient = node.serverConfig.ircclient;
|
||||
|
||||
node.ircclient.addListener('registered', function(message) {
|
||||
//node.log(node.ircclient.nick+" ONLINE");
|
||||
node.status({fill:"yellow",shape:"dot",text:"connected"});
|
||||
node.ircclient.join( node.channel, function(data) {
|
||||
// node.log(data+" JOINED "+node.channel);
|
||||
node.status({fill:"green",shape:"dot",text:"joined"});
|
||||
});
|
||||
});
|
||||
node.ircclient.addListener('message', function (from, to, message) {
|
||||
//node.log(from + ' => ' + to + ' : ' + message);
|
||||
if (~node.channel.toLowerCase().indexOf(to.toLowerCase())) {
|
||||
var msg = { "topic":from, "from":from, "to":to, "payload":message };
|
||||
node.send([msg,null]);
|
||||
}
|
||||
//else { console.log(node.channel,to); }
|
||||
});
|
||||
node.ircclient.addListener('pm', function(from, message) {
|
||||
//node.log("PM => "+from + ': ' + message);
|
||||
var msg = { "topic":from, "from":from, "to":"PRIV", "payload":message };
|
||||
node.send([msg,null]);
|
||||
});
|
||||
node.ircclient.addListener('join', function(channel, who) {
|
||||
var msg = { "payload": { "type":"join", "who":who, "channel":channel } };
|
||||
node.send([null,msg]);
|
||||
//node.log(who+' has joined '+channel);
|
||||
});
|
||||
node.ircclient.addListener('invite', function(channel, from, message) {
|
||||
var msg = { "payload": { "type":"invite", "who":from, "channel":channel, "message":message } };
|
||||
node.send([null,msg]);
|
||||
//node.log(from+' sent invite to '+channel+': '+message);
|
||||
});
|
||||
node.ircclient.addListener('part', function(channel, who, reason) {
|
||||
var msg = { "payload": { "type":"part", "who":who, "channel":channel, "reason":reason } };
|
||||
node.send([null,msg]);
|
||||
//node.log(who+' has left '+channel+': '+reason);
|
||||
});
|
||||
node.ircclient.addListener('quit', function(nick, reason, channels, message) {
|
||||
var msg = { "payload": { "type":"quit", "who":nick, "channel":channels, "reason":reason } };
|
||||
node.send([null,msg]);
|
||||
//node.log(nick+' has quit '+channels+': '+reason);
|
||||
});
|
||||
node.ircclient.addListener('kick', function(channel, who, by, reason) {
|
||||
var msg = { "payload": { "type":"kick", "who":who, "channel":channel, "by":by, "reason":reason } };
|
||||
node.send([null,msg]);
|
||||
//node.log(who+' was kicked from '+channel+' by '+by+': '+reason);
|
||||
});
|
||||
node.ircclient.addListener('names', function (channel, nicks) {
|
||||
var msg = { "payload": { "type": "names", "channel": channel, "names": nicks} };
|
||||
node.send([null, msg]);
|
||||
});
|
||||
node.ircclient.addListener('raw', function (message) { // any message means we are alive
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
});
|
||||
node.on("close", function() {
|
||||
node.ircclient.removeAllListeners();
|
||||
if (node.recon) { clearInterval(node.recon); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("irc in",IrcInNode);
|
||||
|
||||
|
||||
// The Output Node
|
||||
function IrcOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.sendFlag = n.sendObject;
|
||||
this.ircserver = n.ircserver;
|
||||
this.serverConfig = RED.nodes.getNode(this.ircserver);
|
||||
this.channel = n.channel || this.serverConfig.channel;
|
||||
var node = this;
|
||||
if (node.serverConfig.ircclient === null) {
|
||||
node.log("CONNECT: "+node.serverConfig.server);
|
||||
node.status({fill:"grey",shape:"dot",text:"connecting"});
|
||||
node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:true,autoRejoin:false,floodProtection:true,retryDelay:20000});
|
||||
node.serverConfig.ircclient.setMaxListeners(0);
|
||||
node.serverConfig.ircclient.addListener('error', function(message) {
|
||||
if (RED.settings.verbose) { node.log("ERR: "+JSON.stringify(message)); }
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('netError', function(message) {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
if (RED.settings.verbose) { node.log("NET: "+JSON.stringify(message)); }
|
||||
node.status({fill:"red",shape:"ring",text:"net error"});
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('connect', function() {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
if (RED.settings.verbose) { node.log("CONNECTED "); }
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('registered', function(message) {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
node.log(node.serverConfig.ircclient.nick+" ONLINE: "+message.server);
|
||||
node.status({fill:"yellow",shape:"dot",text:"connected"});
|
||||
node.serverConfig.ircclient.join( node.channel, function(data) {
|
||||
node.log(data+" JOINED: "+node.channel);
|
||||
node.status({fill:"green",shape:"dot",text:"joined"});
|
||||
});
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('ping', function(server) {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
if (RED.settings.verbose) { node.log("PING from "+JSON.stringify(server)); }
|
||||
node.status({fill:"green",shape:"dot",text:"ok"});
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('quit', function(nick, reason, channels, message) {
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
if (RED.settings.verbose) { node.log("QUIT: "+nick+" "+reason+" "+channels+" "+JSON.stringify(message)); }
|
||||
node.status({fill:"grey",shape:"ring",text:"quit"});
|
||||
//node.serverConfig.ircclient.disconnect( function() {
|
||||
// node.serverConfig.ircclient.connect();
|
||||
//});
|
||||
//if (RED.settings.verbose) { node.log("restart"); } // then retry
|
||||
});
|
||||
node.serverConfig.ircclient.addListener('raw', function (message) { // any message received means we are alive
|
||||
//console.log("RAW:"+JSON.stringify(message));
|
||||
if (message.commandType === "reply") {
|
||||
//console.log("RAW:"+JSON.stringify(message));
|
||||
node.serverConfig.lastseen = Date.now();
|
||||
}
|
||||
});
|
||||
node.recon = setInterval( function() {
|
||||
//console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000);
|
||||
if ((Date.now()-node.serverConfig.lastseen) > 240000) { // if more than 4 mins since last seen
|
||||
node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
|
||||
}
|
||||
if ((Date.now()-node.serverConfig.lastseen) > 300000) { // If more than 5 mins
|
||||
//node.serverConfig.ircclient.disconnect();
|
||||
//node.serverConfig.ircclient.connect();
|
||||
node.status({fill:"grey",shape:"ring",text:"no connection"});
|
||||
if (RED.settings.verbose) { node.log("CONNECTION LOST ?"); }
|
||||
}
|
||||
//node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
|
||||
}, 60000); // check every 1 min
|
||||
//node.serverConfig.ircclient.connect();
|
||||
}
|
||||
else { node.status({text:""}); }
|
||||
node.ircclient = node.serverConfig.ircclient;
|
||||
|
||||
node.on("input", function(msg) {
|
||||
if (Object.prototype.toString.call( msg.raw ) === '[object Array]') {
|
||||
if (RED.settings.verbose) { node.log("RAW command:"+msg.raw); }
|
||||
node.ircclient.send.apply(node.ircclient,msg.raw);
|
||||
}
|
||||
else {
|
||||
if (msg._topic) { delete msg._topic; }
|
||||
var ch = node.channel.split(","); // split on , so we can send to multiple
|
||||
if (node.sendFlag == "true") { // override channels with msg.topic
|
||||
if ((msg.hasOwnProperty('topic'))&&(typeof msg.topic === "string")) {
|
||||
ch = msg.topic.split(","); // split on , so we can send to multiple
|
||||
}
|
||||
else { node.warn("msg.topic not set"); }
|
||||
}
|
||||
for (var c = 0; c < ch.length; c++) {
|
||||
if (node.sendFlag == "false") { // send whole message object to each channel
|
||||
node.ircclient.say(ch[c], JSON.stringify(msg));
|
||||
}
|
||||
else { // send just the payload to each channel
|
||||
if (typeof msg.payload === "object") { msg.payload = JSON.stringify(msg.payload); }
|
||||
node.ircclient.say(ch[c], msg.payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.ircclient.removeAllListeners();
|
||||
if (node.recon) { clearInterval(node.recon); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("irc out",IrcOutNode);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="tail">
|
||||
<div class="form-row node-input-filename">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-split" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-split" style="width: 70%;">Split lines if we see \n ?</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<!-- <div class="form-tips">WON'T work on Windows.</div> -->
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="tail">
|
||||
<p>Tails (watches for things to be added) to the configured file. (Linux/Mac ONLY)</p>
|
||||
<p>This won't work on Windows filesystems, as it relies on the tail -F command.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('tail',{
|
||||
category: 'storage-input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
split: {value:false},
|
||||
filename: {value:"",required:true}
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "file.png",
|
||||
label: function() {
|
||||
return this.name||this.filename;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var spawn = require('child_process').spawn;
|
||||
var plat = require('os').platform();
|
||||
|
||||
if (plat.match(/^win/)) {
|
||||
throw "Info : Currently not supported on Windows.";
|
||||
}
|
||||
|
||||
function TailNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.filename = n.filename;
|
||||
this.split = n.split;
|
||||
var node = this;
|
||||
|
||||
var err = "";
|
||||
// TODO: rewrite to use node-tail
|
||||
var tail = spawn("tail", ["-F", "-n", "0", this.filename]);
|
||||
tail.stdout.on("data", function (data) {
|
||||
if (node.split) {
|
||||
// TODO: allow customisation of the line break - as we do elsewhere
|
||||
var strings = data.toString().split("\n");
|
||||
for (var s in strings) {
|
||||
//TODO: should we really filter blanks? Is that expected?
|
||||
if (strings[s] !== "") {
|
||||
node.send({
|
||||
topic: node.filename,
|
||||
payload: strings[s]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var msg = {
|
||||
topic:node.filename,
|
||||
payload: data.toString()
|
||||
};
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
tail.stderr.on("data", function(data) {
|
||||
node.warn(data.toString());
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
if (tail) { tail.kill(); }
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("tail",TailNode);
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013, 2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="file">
|
||||
<div class="form-row node-input-filename">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> Action</label>
|
||||
<select type="text" id="node-input-overwriteFile" style="display: inline-block; width: 250px; vertical-align: top;">
|
||||
<option value="false">append to file</option>
|
||||
<option value="true">overwrite file</option>
|
||||
<option value="delete">delete file</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-appline">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-appendNewline" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-appendNewline" style="width: 70%;">Add newline (\n) to each payload ?</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="file">
|
||||
<p>Writes <b>msg.payload</b> to the file specified, e.g. to create a log.</p>
|
||||
<p>The filename can be configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
|
||||
<p>A newline is added to every message. But this can be turned off if required, for example, to allow binary files to be written.</p>
|
||||
<p>The default behaviour is to append to the file. This can be changed to overwrite the file each time, for example if you want to output a "static" web page or report.</p>
|
||||
<p>This node can also be configured to delete a file if required. <i>Note:</i> Using msg.delete is now deprecated.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="file in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-format"><i class="fa fa-sign-out"></i> Output as</label>
|
||||
<select id="node-input-format">
|
||||
<option value="utf8">a utf8 string</option>
|
||||
<option value="">a Buffer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="file in">
|
||||
<p>Reads the specified file and sends the content as <b>msg.payload</b>, and the filename as <b>msg.filename</b>.</p>
|
||||
<p>The filename can be configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('file',{
|
||||
category: 'storage-output',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
filename: {value:""},
|
||||
appendNewline: {value:true},
|
||||
overwriteFile: {value:"false"}
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "file.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
if (this.overwriteFile === "delete") { return this.name||"delete "+this.filename; }
|
||||
else { return this.name||this.filename; }
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-overwriteFile").on("change",function() {
|
||||
if (this.value === "delete") { $("#node-appline").hide(); }
|
||||
else { $("#node-appline").show(); }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('file in',{
|
||||
category: 'storage-input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
filename: {value:""},
|
||||
format: {value:"utf8"},
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "file.png",
|
||||
label: function() {
|
||||
return this.name||this.filename;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
@@ -1,119 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var fs = require("fs");
|
||||
|
||||
function FileNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.filename = n.filename || "";
|
||||
this.appendNewline = n.appendNewline;
|
||||
this.overwriteFile = n.overwriteFile.toString();
|
||||
var node = this;
|
||||
this.on("input",function(msg) {
|
||||
var filename;
|
||||
if (msg.filename) {
|
||||
if (n.filename && (n.filename !== msg.filename)) {
|
||||
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
filename = msg.filename;
|
||||
node.status({fill:"grey",shape:"dot",text:msg.filename});
|
||||
} else {
|
||||
filename = this.filename;
|
||||
}
|
||||
if (filename === "") {
|
||||
node.warn('No filename specified');
|
||||
} else if (msg.hasOwnProperty('delete')) {
|
||||
node.warn("Deprecated: please use specific delete option in config dialog.");
|
||||
fs.unlink(filename, function (err) {
|
||||
if (err) { node.warn('Failed to delete file : '+err); }
|
||||
});
|
||||
} else if (typeof msg.payload != "undefined") {
|
||||
var data = msg.payload;
|
||||
if ((typeof data === "object")&&(!Buffer.isBuffer(data))) {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
if (typeof data === "boolean") { data = data.toString(); }
|
||||
if ((this.appendNewline)&&(!Buffer.isBuffer(data))) { data += "\n"; }
|
||||
if (this.overwriteFile === "true") {
|
||||
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
|
||||
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
|
||||
fs.writeFile(filename, data, "binary", function (err) {
|
||||
if (err) { node.warn('Failed to write to file : '+err); }
|
||||
else if (RED.settings.verbose) { node.log('wrote to file: '+filename); }
|
||||
});
|
||||
}
|
||||
else if (this.overwriteFile === "delete") {
|
||||
fs.unlink(filename, function (err) {
|
||||
if (err) { node.warn('Failed to delete file : '+err); }
|
||||
else if (RED.settings.verbose) { node.log("deleted file: "+filename); }
|
||||
});
|
||||
}
|
||||
else {
|
||||
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer
|
||||
//fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
|
||||
fs.appendFile(filename, data, "binary", function (err) {
|
||||
if (err) { node.warn('Failed to append to file : '+err); }
|
||||
else if (RED.settings.verbose) { node.log('appended to file: '+filename); }
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("file",FileNode);
|
||||
|
||||
|
||||
function FileInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.filename = n.filename || "";
|
||||
this.format = n.format;
|
||||
var node = this;
|
||||
var options = {};
|
||||
if (this.format) {
|
||||
options['encoding'] = this.format;
|
||||
}
|
||||
this.on("input",function(msg) {
|
||||
var filename;
|
||||
if (msg.filename) {
|
||||
if (n.filename && (n.filename !== msg.filename)) {
|
||||
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
filename = msg.filename;
|
||||
} else {
|
||||
filename = this.filename;
|
||||
}
|
||||
if (filename === "") {
|
||||
node.warn('No filename specified');
|
||||
} else {
|
||||
msg.filename = filename;
|
||||
fs.readFile(filename,options,function(err,data) {
|
||||
if (err) {
|
||||
node.warn(err);
|
||||
msg.error = err;
|
||||
delete msg.payload;
|
||||
} else {
|
||||
msg.payload = data;
|
||||
delete msg.error;
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("file in",FileInNode);
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="redis out">
|
||||
<div class="form-row node-input-hostname">
|
||||
<label for="node-input-hostname"><i class="fa fa-bookmark"></i> Host</label>
|
||||
<input class="input-append-left" type="text" id="node-input-hostname" placeholder="127.0.0.1" style="width: 40%;" ><button id="node-input-hostname-lookup" class="btn input-append-right"><span class="caret"></span></button>
|
||||
<label for="node-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
|
||||
<input type="text" id="node-input-port" placeholder="6379" style="width:45px">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-key"><i class="fa fa-key"></i> Key</label>
|
||||
<input type="text" id="node-input-key" placeholder="Redis Key">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-type"><i class="fa fa-th"></i> Type</label>
|
||||
<select type="text" id="node-input-structtype" style="width: 150px;">
|
||||
<option value="string">String</option>
|
||||
<option value="hash">Hash</option>
|
||||
<option value="set">Set</option>
|
||||
<option value="list">List</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
If key is blank, the topic will be used as the key.<br>
|
||||
If type is hash, payload should be an object or field=value string.
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="redis out">
|
||||
<p>A Redis output node. Options include Hash, Set, List and String.</p>
|
||||
<p>To run this you need a local Redis server running. For details see <a href="http://redis.io/" target="_new">the Redis site</a>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('redis out',{
|
||||
category: 'storage-output',
|
||||
color:"#ffaaaa",
|
||||
defaults: {
|
||||
hostname: { value:"127.0.0.1",required:true},
|
||||
port: { value: 6379,required:true},
|
||||
name: {value:""},
|
||||
key: {value:""},
|
||||
structtype: {value:"",required:true}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "redis.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||this.key+" ("+this.structtype+")";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var availableServers = [];
|
||||
var matchedServers = {};
|
||||
RED.nodes.eachNode(function(node) {
|
||||
if (node.type == "redis out" && node.hostname && node.port && !matchedServers[node.hostname+":"+node.port]) {
|
||||
var label = node.hostname+":"+node.port;
|
||||
matchedServers[label] = true;
|
||||
availableServers.push({
|
||||
label:label,
|
||||
value:node.hostname,
|
||||
port:node.port
|
||||
});
|
||||
}
|
||||
});
|
||||
$( "#node-input-hostname" ).autocomplete({
|
||||
minLength: 0,
|
||||
source: availableServers,
|
||||
select: function( event, ui ) {
|
||||
$("#node-input-port").val(ui.item.port);
|
||||
}
|
||||
});
|
||||
var tt = this;
|
||||
tt._acOpen = false;
|
||||
$( "#node-input-hostname" ).on( "autocompleteclose", function( event, ui ) { tt._acOpen = false;} );
|
||||
$( "#node-input-hostname-lookup" ).click(function(e) {
|
||||
if (tt._acOpen) {
|
||||
$( "#node-input-hostname" ).autocomplete( "close");
|
||||
} else {
|
||||
$( "#node-input-hostname" ).autocomplete( "search", "" );
|
||||
}
|
||||
tt._acOpen = !tt._acOpen;
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,111 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var redis = require("redis");
|
||||
|
||||
var hashFieldRE = /^([^=]+)=(.*)$/;
|
||||
|
||||
var redisConnectionPool = function() {
|
||||
var connections = {};
|
||||
var obj = {
|
||||
get: function(host,port) {
|
||||
var id = host+":"+port;
|
||||
if (!connections[id]) {
|
||||
connections[id] = redis.createClient(port,host);
|
||||
connections[id].on("error",function(err) {
|
||||
util.log("[redis] "+err);
|
||||
});
|
||||
connections[id].on("connect",function() {
|
||||
util.log("[redis] connected to "+host+":"+port);
|
||||
});
|
||||
connections[id]._id = id;
|
||||
connections[id]._nodeCount = 0;
|
||||
}
|
||||
connections[id]._nodeCount += 1;
|
||||
return connections[id];
|
||||
},
|
||||
close: function(connection) {
|
||||
connection._nodeCount -= 1;
|
||||
if (connection._nodeCount === 0) {
|
||||
if (connection) {
|
||||
clearTimeout(connection.retry_timer);
|
||||
connection.end();
|
||||
}
|
||||
delete connections[connection._id];
|
||||
}
|
||||
}
|
||||
};
|
||||
return obj;
|
||||
}();
|
||||
|
||||
|
||||
function RedisOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.port = n.port||"6379";
|
||||
this.hostname = n.hostname||"127.0.0.1";
|
||||
this.key = n.key;
|
||||
this.structtype = n.structtype;
|
||||
|
||||
this.client = redisConnectionPool.get(this.hostname,this.port);
|
||||
|
||||
if (this.client.connected) {
|
||||
this.status({fill:"green",shape:"dot",text:"connected"});
|
||||
} else {
|
||||
this.status({fill:"red",shape:"ring",text:"disconnected"},true);
|
||||
}
|
||||
|
||||
var node = this;
|
||||
this.client.on("end", function() {
|
||||
node.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
});
|
||||
this.client.on("connect", function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
|
||||
this.on("input", function(msg) {
|
||||
var k = this.key || msg.topic;
|
||||
if (k) {
|
||||
if (this.structtype == "string") {
|
||||
this.client.set(k,RED.util.ensureString(msg.payload));
|
||||
} else if (this.structtype == "hash") {
|
||||
if (typeof msg.payload == "object") {
|
||||
this.client.hmset(k,msg.payload);
|
||||
} else {
|
||||
var r = hashFieldRE.exec(msg.payload);
|
||||
if (r) {
|
||||
this.client.hset(k,r[1],r[2]);
|
||||
} else {
|
||||
this.warn("Invalid payload for redis hash");
|
||||
}
|
||||
}
|
||||
} else if (this.structtype == "set") {
|
||||
this.client.sadd(k,msg.payload);
|
||||
} else if (this.structtype == "list") {
|
||||
this.client.rpush(k,msg.payload);
|
||||
}
|
||||
} else {
|
||||
this.warn("No key or topic set");
|
||||
}
|
||||
});
|
||||
this.on("close", function() {
|
||||
redisConnectionPool.close(node.client);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("redis out",RedisOutNode);
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="mongodb">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-hostname"><i class="fa fa-bookmark"></i> Host</label>
|
||||
<input class="input-append-left" type="text" id="node-config-input-hostname" placeholder="localhost" style="width: 40%;" >
|
||||
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
|
||||
<input type="text" id="node-config-input-port" placeholder="27017" style="width:45px">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-db"><i class="fa fa-database"></i> Database</label>
|
||||
<input type="text" id="node-config-input-db" placeholder="test">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-user"><i class="fa fa-user"></i> Username</label>
|
||||
<input type="text" id="node-config-input-user">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
|
||||
<input type="password" id="node-config-input-password">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-config-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mongodb', {
|
||||
category: 'config',
|
||||
color: "rgb(218, 196, 180)",
|
||||
defaults: {
|
||||
hostname: {value: "127.0.0.1", required: true},
|
||||
port: {value: 27017, required: true},
|
||||
db: {value: "", required: true},
|
||||
name: {value: ""}
|
||||
},
|
||||
credentials: {
|
||||
user: {type: "text"},
|
||||
password: {type: "password"}
|
||||
},
|
||||
label: function() {
|
||||
return this.name || this.hostname + ":" + this.port + "/" + this.db;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="mongodb out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-mongodb"><i class="fa fa-bookmark"></i> Server</label>
|
||||
<input type="text" id="node-input-mongodb">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-collection"><i class="fa fa-briefcase"></i> Collection</label>
|
||||
<input type="text" id="node-input-collection" placeholder="collection">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
|
||||
<select type="text" id="node-input-operation" style="display: inline-block; vertical-align: top;">
|
||||
<option value="store">save</option>
|
||||
<option value="insert">insert</option>
|
||||
<option value="update">update</option>
|
||||
<option value="delete">remove</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-payonly">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-payonly" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-payonly" style="width: 70%;">Only store msg.payload object</label>
|
||||
</div>
|
||||
<div class="form-row node-input-upsert">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-upsert" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-upsert" style="width: 70%;">Create a new document if no match found</label>
|
||||
</div>
|
||||
<div class="form-row node-input-multi">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-multi" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;;">
|
||||
<label for="node-input-multi" style="width: 70%;">Update all matching documents</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips" id="node-warning" style="display: none"><b> Tip:</b> If no collection is set, ensure <b>msg.collection</b> will contain the collection name
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mongodb out">
|
||||
<p>A simple MongoDB output node. Can save, insert, update and remove objects from a chosen collection.</p>
|
||||
<p>Save will update an existing object or insert a new object if one does not already exist.</p>
|
||||
<p>Insert will insert a new object.</p>
|
||||
<p>Save and insert either store <b>msg</b> or <b>msg.payload</b>.</p>
|
||||
<p>Update will modify an existing object or objects. The query to select objects to update uses <b>msg.query</b> and the update to the element uses <b>msg.payload</b>.</p>
|
||||
<p>Update can add a object if it does not exist or update multiple objects.</p>
|
||||
<p>Remove will remove objects that match the query passed in on <b>msg.payload</b>. A blank query will delete <i>all of the objects</i> in the collection.</p>
|
||||
<p>You can either set the collection method in the node config or on <b>msg.collection</b>. Setting it in the node will override <b>msg.collection</b>.</p>
|
||||
<p>By default MongoDB creates an <i>_id</i> property as the primary key - so repeated injections of the same <b>msg</b> will result in many database entries.</p>
|
||||
<p>If this is NOT the desired behaviour - ie. you want repeated entries to overwrite, then you must set the <b>msg._id</b> property to be a constant by the use of a previous function node.</p>
|
||||
<p>This could be a unique constant or you could create one based on some other msg property.</p>
|
||||
<p>Currently we do not limit or cap the collection size at all... this may well change.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function oneditprepare() {
|
||||
$("#node-input-operation").change(function () {
|
||||
var id = $("#node-input-operation option:selected").val();
|
||||
|
||||
if (id === "update") {
|
||||
$(".node-input-payonly").hide();
|
||||
$(".node-input-upsert, .node-input-multi").show();
|
||||
} else if (id === "delete") {
|
||||
$(".node-input-payonly, .node-input-upsert, .node-input-multi").hide();
|
||||
} else {
|
||||
$(".node-input-payonly").show();
|
||||
$(".node-input-upsert, .node-input-multi").hide();
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-collection").change(function () {
|
||||
if($("#node-input-collection").val() === "") {
|
||||
$("#node-warning").show();
|
||||
} else {
|
||||
$("#node-warning").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('mongodb out', {
|
||||
category: 'storage-output',
|
||||
color: "rgb(218, 196, 180)",
|
||||
defaults: {
|
||||
mongodb: {type: "mongodb", required: true},
|
||||
name: {value: ""},
|
||||
collection: {value: ""},
|
||||
payonly: {value: false},
|
||||
upsert: {value: false},
|
||||
multi: {value: false},
|
||||
operation: {value: "store"}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 0,
|
||||
icon: "mongodb.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
var mongoNode = RED.nodes.node(this.mongodb);
|
||||
return this.name || (mongoNode ? mongoNode.label() + " " + this.collection: "mongodb");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: oneditprepare
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="mongodb in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-mongodb"><i class="fa fa-bookmark"></i> Server</label>
|
||||
<input type="text" id="node-input-mongodb">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-collection"><i class="fa fa-briefcase"></i> Collection</label>
|
||||
<input type="text" id="node-input-collection" placeholder="collection">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
|
||||
<select type="text" id="node-input-operation" style="display: inline-block; vertical-align: top;">
|
||||
<option value="find">find</option>
|
||||
<option value="count">count</option>
|
||||
<option value="aggregate">aggregate</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips" id="node-warning" style="display: none"><b> Tip:</b> If no collection is set, ensure <b>msg.collection</b> will contain the collection name
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mongodb in">
|
||||
<p>Calls a MongoDB collection method based on the selected operator.</p>
|
||||
<p>Find queries a collection using the <b>msg.payload</b> as the query statement as per the .find() function. Optionally, you may also (via a function) set a <b>msg.projection</b> object to constrain the returned fields, a <b>msg.sort</b> object, a <b>msg.limit</b> number and a <b>msg.skip</b> number.</p>
|
||||
<p>Count returns a count of the number of documents in a collection or matching a query using the <b>msg.payload</b> as the query statement.</p>
|
||||
<p>Aggregate provides access to the aggregation pipeline using the <b>msg.payload</b> as the pipeline array.</p>
|
||||
<p>You can either set the collection method in the node config or on <b>msg.collection</b>. Setting it in the node will override <b>msg.collection</b>.</p>
|
||||
<p>See the <a href="http://docs.mongodb.org/manual/reference/method/db.collection.find/" target="new"><i>MongoDB collection methods docs</i></a> for examples.</p>
|
||||
<p>The result is returned in <b>msg.payload</b>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
RED.nodes.registerType('mongodb in', {
|
||||
category: 'storage-input',
|
||||
color: "rgb(218, 196, 180)",
|
||||
defaults: {
|
||||
mongodb: {type: "mongodb", required: true},
|
||||
name: {value: ""},
|
||||
collection: {value: ""},
|
||||
operation: {value: "find"}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "mongodb.png",
|
||||
label: function() {
|
||||
var mongoNode = RED.nodes.node(this.mongodb);
|
||||
return this.name || (mongoNode ? mongoNode.label() + " " + this.collection: "mongodb");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: oneditprepare
|
||||
});
|
||||
</script>
|
||||
@@ -1,234 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var mongo = require('mongodb');
|
||||
var MongoClient = mongo.MongoClient;
|
||||
|
||||
function MongoNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.hostname = n.hostname;
|
||||
this.port = n.port;
|
||||
this.db = n.db;
|
||||
this.name = n.name;
|
||||
|
||||
var url = "mongodb://";
|
||||
if (this.credentials && this.credentials.user && this.credentials.password) {
|
||||
url += this.credentials.user+":"+this.credentials.password+"@";
|
||||
}
|
||||
url += this.hostname+":"+this.port+"/"+this.db;
|
||||
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
RED.nodes.registerType("mongodb",MongoNode,{
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
}
|
||||
});
|
||||
|
||||
function ensureValidSelectorObject(selector) {
|
||||
if (selector != null && (typeof selector != 'object' || Buffer.isBuffer(selector))) {
|
||||
return {};
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
|
||||
|
||||
function MongoOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.collection = n.collection;
|
||||
this.mongodb = n.mongodb;
|
||||
this.payonly = n.payonly || false;
|
||||
this.upsert = n.upsert || false;
|
||||
this.multi = n.multi || false;
|
||||
this.operation = n.operation;
|
||||
this.mongoConfig = RED.nodes.getNode(this.mongodb);
|
||||
|
||||
if (this.mongoConfig) {
|
||||
var node = this;
|
||||
MongoClient.connect(this.mongoConfig.url, function(err, db) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
} else {
|
||||
node.clientDb = db;
|
||||
var coll;
|
||||
if (node.collection) {
|
||||
coll = db.collection(node.collection);
|
||||
}
|
||||
node.on("input",function(msg) {
|
||||
if (!node.collection) {
|
||||
if (msg.collection) {
|
||||
coll = db.collection(msg.collection);
|
||||
} else {
|
||||
node.error("No collection defined");
|
||||
return;
|
||||
}
|
||||
}
|
||||
delete msg._topic;
|
||||
delete msg.collection;
|
||||
if (node.operation === "store") {
|
||||
if (node.payonly) {
|
||||
if (typeof msg.payload !== "object") {
|
||||
msg.payload = {"payload": msg.payload};
|
||||
}
|
||||
coll.save(msg.payload,function(err, item) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
coll.save(msg,function(err, item) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (node.operation === "insert") {
|
||||
if (node.payonly) {
|
||||
if (typeof msg.payload !== "object") {
|
||||
msg.payload = {"payload": msg.payload};
|
||||
}
|
||||
coll.insert(msg.payload, function(err, item) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
coll.insert(msg, function(err,item) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (node.operation === "update") {
|
||||
if (typeof msg.payload !== "object") {
|
||||
msg.payload = {"payload": msg.payload};
|
||||
}
|
||||
var query = msg.query || {};
|
||||
var payload = msg.payload || {};
|
||||
var options = {
|
||||
upsert: node.upsert,
|
||||
multi: node.multi
|
||||
};
|
||||
|
||||
coll.update(query, payload, options, function(err, item) {
|
||||
if (err) {
|
||||
node.error(err + " " + payload);
|
||||
}
|
||||
});
|
||||
} else if (node.operation === "delete") {
|
||||
coll.remove(msg.payload, function(err, items) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.error("missing mongodb configuration");
|
||||
}
|
||||
|
||||
this.on("close", function() {
|
||||
if (this.clientDb) {
|
||||
this.clientDb.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mongodb out",MongoOutNode);
|
||||
|
||||
function MongoInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.collection = n.collection;
|
||||
this.mongodb = n.mongodb;
|
||||
this.operation = n.operation || "find";
|
||||
this.mongoConfig = RED.nodes.getNode(this.mongodb);
|
||||
|
||||
if (this.mongoConfig) {
|
||||
var node = this;
|
||||
MongoClient.connect(this.mongoConfig.url, function(err,db) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
} else {
|
||||
node.clientDb = db;
|
||||
var coll;
|
||||
if (node.collection) {
|
||||
coll = db.collection(node.collection);
|
||||
}
|
||||
node.on("input", function(msg) {
|
||||
if (!node.collection) {
|
||||
if (msg.collection) {
|
||||
coll = db.collection(msg.collection);
|
||||
} else {
|
||||
node.error("No collection defined");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (node.operation === "find") {
|
||||
msg.projection = msg.projection || {};
|
||||
var selector = ensureValidSelectorObject(msg.payload);
|
||||
coll.find(selector,msg.projection).sort(msg.sort).limit(msg.limit).skip(msg.skip).toArray(function(err, items) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
} else {
|
||||
msg.payload = items;
|
||||
delete msg.projection;
|
||||
delete msg.sort;
|
||||
delete msg.limit;
|
||||
delete msg.skip;
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
} else if (node.operation === "count") {
|
||||
var selector = ensureValidSelectorObject(msg.payload);
|
||||
coll.count(selector, function(err, count) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
} else {
|
||||
msg.payload = count;
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
} else if (node.operation === "aggregate") {
|
||||
msg.payload = (Array.isArray(msg.payload)) ? msg.payload : [];
|
||||
coll.aggregate(msg.payload, function(err, result) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
} else {
|
||||
msg.payload = result;
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.error("missing mongodb configuration");
|
||||
}
|
||||
|
||||
this.on("close", function() {
|
||||
if (this.clientDb) {
|
||||
this.clientDb.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mongodb in",MongoInNode);
|
||||
}
|
||||
168
package.json
168
package.json
@@ -1,71 +1,121 @@
|
||||
{
|
||||
"name" : "node-red",
|
||||
"version" : "0.10.0",
|
||||
"description" : "A visual tool for wiring the Internet of Things",
|
||||
"homepage" : "http://nodered.org",
|
||||
"license" : "Apache",
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
"url":"https://github.com/node-red/node-red.git"
|
||||
"name": "node-red",
|
||||
"version": "0.20.0-beta.3",
|
||||
"description": "A visual tool for wiring the Internet of Things",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/node-red/node-red.git"
|
||||
},
|
||||
"main" : "red/red.js",
|
||||
"scripts" : {
|
||||
"start": "node red.js",
|
||||
"test": "./node_modules/.bin/grunt"
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
"start": "node packages/node_modules/node-red/red.js",
|
||||
"test": "grunt",
|
||||
"build": "grunt build",
|
||||
"docs": "grunt docs"
|
||||
},
|
||||
"contributors": [
|
||||
{"name": "Nick O'Leary"},
|
||||
{"name": "Dave Conway-Jones"}
|
||||
],
|
||||
"keywords": [
|
||||
"editor", "messaging", "iot", "m2m", "pi", "arduino", "beaglebone", "ibm", "flow"
|
||||
{
|
||||
"name": "Nick O'Leary"
|
||||
},
|
||||
{
|
||||
"name": "Dave Conway-Jones"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"express": "3.17.2",
|
||||
"when": "3.7.2",
|
||||
"bcryptjs": "2.1.0",
|
||||
"nopt": "3.0.1",
|
||||
"mqtt": "0.3.x",
|
||||
"ws": "0.7.1",
|
||||
"fs-extra": "0.16.3",
|
||||
"clone": "0.2.0",
|
||||
"mustache": "1.0.0",
|
||||
"cron":"1.0.6",
|
||||
"raw-body":"1.3.2",
|
||||
"twitter-ng":"0.6.2",
|
||||
"oauth":"0.9.12",
|
||||
"xml2js":"0.4.4",
|
||||
"sentiment":"0.2.3",
|
||||
"irc":"0.3.9",
|
||||
"follow-redirects":"0.0.3",
|
||||
"cors":"2.5.3",
|
||||
"mkdirp":"0.5.0",
|
||||
"cheerio":"0.18.0",
|
||||
"uglify-js":"2.4.16",
|
||||
"nodemailer":"1.3.0",
|
||||
"imap":"0.8.14",
|
||||
"request":"2.42.0",
|
||||
"on-headers":"1.0.0",
|
||||
"is-utf8":"0.2.0",
|
||||
"serialport":"1.4.10",
|
||||
"feedparser":"0.19.2",
|
||||
"fs.notify":"0.0.4",
|
||||
"passport":"0.2.1",
|
||||
"passport-http-bearer":"1.0.1",
|
||||
"passport-oauth2-client-password":"0.1.2",
|
||||
"oauth2orize":"1.0.1"
|
||||
"ajv": "6.6.2",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.18.3",
|
||||
"cheerio": "0.22.0",
|
||||
"clone": "2.1.2",
|
||||
"cookie": "0.3.1",
|
||||
"cookie-parser": "1.4.3",
|
||||
"cors": "2.8.5",
|
||||
"cron": "1.6.0",
|
||||
"denque": "1.4.0",
|
||||
"express": "4.16.4",
|
||||
"express-session": "1.15.6",
|
||||
"fs-extra": "7.0.1",
|
||||
"fs.notify": "0.0.4",
|
||||
"hash-sum": "1.0.2",
|
||||
"https-proxy-agent": "2.2.1",
|
||||
"i18next": "13.1.0",
|
||||
"is-utf8": "0.2.1",
|
||||
"js-yaml": "3.12.1",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"jsonata": "1.6.3",
|
||||
"media-typer": "1.0.1",
|
||||
"memorystore": "1.6.0",
|
||||
"mime": "2.4.0",
|
||||
"mqtt": "2.18.8",
|
||||
"multer": "1.4.1",
|
||||
"mustache": "3.0.1",
|
||||
"node-red-node-email": "1.0.*",
|
||||
"node-red-node-feedparser": "^0.1.14",
|
||||
"node-red-node-rbe": "0.2.*",
|
||||
"node-red-node-sentiment": "^0.1.0",
|
||||
"node-red-node-tail": "^0.0.1",
|
||||
"node-red-node-twitter": "^1.1.0",
|
||||
"nopt": "4.0.1",
|
||||
"oauth2orize": "1.11.0",
|
||||
"on-headers": "1.0.1",
|
||||
"passport": "0.4.0",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"raw-body": "2.3.3",
|
||||
"request": "2.88.0",
|
||||
"semver": "5.6.0",
|
||||
"sentiment": "2.1.0",
|
||||
"uglify-js": "3.4.9",
|
||||
"when": "3.7.8",
|
||||
"ws": "6.1.2",
|
||||
"xml2js": "0.4.19"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "~2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "0.4.5",
|
||||
"grunt-cli": "0.1.13",
|
||||
"grunt-simple-mocha": "0.4.0",
|
||||
"grunt-contrib-jshint": "0.11.0",
|
||||
"mocha": "2.1.0",
|
||||
"should": "4.6.5",
|
||||
"sinon": "1.12.2",
|
||||
"supertest": "0.15.0"
|
||||
"chromedriver": "2.45.0",
|
||||
"grunt": "~1.0.3",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-cli": "~1.3.2",
|
||||
"grunt-concurrent": "~2.3.1",
|
||||
"grunt-contrib-clean": "~1.1.0",
|
||||
"grunt-contrib-compress": "~1.4.0",
|
||||
"grunt-contrib-concat": "~1.0.1",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-contrib-jshint": "~1.1.0",
|
||||
"grunt-contrib-uglify": "~3.4.0",
|
||||
"grunt-contrib-watch": "~1.1.0",
|
||||
"grunt-jsdoc": "^2.2.1",
|
||||
"grunt-jsdoc-to-markdown": "^4.0.0",
|
||||
"grunt-jsonlint": "~1.1.0",
|
||||
"grunt-mkdir": "~1.0.0",
|
||||
"grunt-mocha-istanbul": "5.0.2",
|
||||
"grunt-nodemon": "~0.4.2",
|
||||
"grunt-npm-command": "~0.1.2",
|
||||
"grunt-sass": "~2.0.0",
|
||||
"grunt-simple-mocha": "~0.4.1",
|
||||
"grunt-webdriver": "^2.0.3",
|
||||
"http-proxy": "^1.16.2",
|
||||
"istanbul": "0.4.5",
|
||||
"minami": "1.2.3",
|
||||
"mocha": "^5.2.0",
|
||||
"mosca": "^2.8.3",
|
||||
"should": "^8.4.0",
|
||||
"sinon": "1.17.7",
|
||||
"stoppable": "^1.1.0",
|
||||
"supertest": "3.3.0",
|
||||
"wdio-chromedriver-service": "^0.1.5",
|
||||
"wdio-mocha-framework": "^0.6.4",
|
||||
"wdio-spec-reporter": "^0.1.5",
|
||||
"webdriverio": "^4.14.1",
|
||||
"node-red-node-test-helper": "node-red/node-red-node-test-helper",
|
||||
"jsdoc-nr-template": "node-red/jsdoc-nr-template"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/node_modules/@node-red/editor-api/.npmignore
vendored
Normal file
2
packages/node_modules/@node-red/editor-api/.npmignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
src
|
||||
docs
|
||||
178
packages/node_modules/@node-red/editor-api/LICENSE
vendored
Normal file
178
packages/node_modules/@node-red/editor-api/LICENSE
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
12
packages/node_modules/@node-red/editor-api/README.md
vendored
Normal file
12
packages/node_modules/@node-red/editor-api/README.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
@node-red/editor-api
|
||||
====================
|
||||
|
||||
Node-RED editor api module.
|
||||
|
||||
This provides an Express application that can be used to serve the Node-RED
|
||||
editor.
|
||||
|
||||
|
||||
### Source
|
||||
|
||||
The main Node-RED modules are maintained as a monorepo on [GitHub](https://github.com/node-red/node-red).
|
||||
56
packages/node_modules/@node-red/editor-api/lib/admin/context.js
vendored
Normal file
56
packages/node_modules/@node-red/editor-api/lib/admin/context.js
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var apiUtils = require("../util");
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
|
||||
get: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
scope: req.params.scope,
|
||||
id: req.params.id,
|
||||
key: req.params[0],
|
||||
store: req.query['store']
|
||||
}
|
||||
runtimeAPI.context.getValue(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
delete: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
scope: req.params.scope,
|
||||
id: req.params.id,
|
||||
key: req.params[0],
|
||||
store: req.query['store']
|
||||
}
|
||||
runtimeAPI.context.delete(opts).then(function(result) {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
||||
69
packages/node_modules/@node-red/editor-api/lib/admin/flow.js
vendored
Normal file
69
packages/node_modules/@node-red/editor-api/lib/admin/flow.js
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var runtimeAPI;
|
||||
var apiUtils = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
get: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.flows.getFlow(opts).then(function(result) {
|
||||
return res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
post: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
flow: req.body
|
||||
}
|
||||
runtimeAPI.flows.addFlow(opts).then(function(id) {
|
||||
return res.json({id:id});
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
put: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
flow: req.body
|
||||
}
|
||||
runtimeAPI.flows.updateFlow(opts).then(function(id) {
|
||||
return res.json({id:id});
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
delete: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.flows.deleteFlow(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
||||
70
packages/node_modules/@node-red/editor-api/lib/admin/flows.js
vendored
Normal file
70
packages/node_modules/@node-red/editor-api/lib/admin/flows.js
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var runtimeAPI;
|
||||
var apiUtils = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
get: function(req,res) {
|
||||
var version = req.get("Node-RED-API-Version")||"v1";
|
||||
if (!/^v[12]$/.test(version)) {
|
||||
return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
|
||||
}
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
runtimeAPI.flows.getFlows(opts).then(function(result) {
|
||||
if (version === "v1") {
|
||||
res.json(result.flows);
|
||||
} else if (version === "v2") {
|
||||
res.json(result);
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
post: function(req,res) {
|
||||
var version = req.get("Node-RED-API-Version")||"v1";
|
||||
if (!/^v[12]$/.test(version)) {
|
||||
return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
|
||||
}
|
||||
var opts = {
|
||||
user: req.user,
|
||||
deploymentType: req.get("Node-RED-Deployment-Type")||"full"
|
||||
}
|
||||
|
||||
if (opts.deploymentType !== 'reload') {
|
||||
if (version === "v1") {
|
||||
opts.flows = {flows: req.body}
|
||||
} else {
|
||||
opts.flows = req.body;
|
||||
}
|
||||
}
|
||||
|
||||
runtimeAPI.flows.setFlows(opts).then(function(result) {
|
||||
if (version === "v1") {
|
||||
res.status(204).end();
|
||||
} else {
|
||||
res.json(result);
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
||||
72
packages/node_modules/@node-red/editor-api/lib/admin/index.js
vendored
Normal file
72
packages/node_modules/@node-red/editor-api/lib/admin/index.js
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var express = require("express");
|
||||
|
||||
var nodes = require("./nodes");
|
||||
var flows = require("./flows");
|
||||
var flow = require("./flow");
|
||||
var context = require("./context");
|
||||
var auth = require("../auth");
|
||||
|
||||
var apiUtil = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(runtimeAPI) {
|
||||
flows.init(runtimeAPI);
|
||||
flow.init(runtimeAPI);
|
||||
nodes.init(runtimeAPI);
|
||||
context.init(runtimeAPI);
|
||||
|
||||
var needsPermission = auth.needsPermission;
|
||||
|
||||
var adminApp = express();
|
||||
|
||||
// Flows
|
||||
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
||||
adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler);
|
||||
|
||||
// Flow
|
||||
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,apiUtil.errorHandler);
|
||||
adminApp.post("/flow",needsPermission("flows.write"),flow.post,apiUtil.errorHandler);
|
||||
adminApp.delete("/flow/:id",needsPermission("flows.write"),flow.delete,apiUtil.errorHandler);
|
||||
adminApp.put("/flow/:id",needsPermission("flows.write"),flow.put,apiUtil.errorHandler);
|
||||
|
||||
// Nodes
|
||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
||||
adminApp.get(/\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
|
||||
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
|
||||
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
||||
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,apiUtil.errorHandler);
|
||||
adminApp.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,apiUtil.errorHandler);
|
||||
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
|
||||
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
|
||||
|
||||
// Context
|
||||
adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||
adminApp.get("/context/:scope(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||
adminApp.get("/context/:scope(node|flow)/:id",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||
adminApp.get("/context/:scope(node|flow)/:id/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||
|
||||
// adminApp.delete("/context/:scope(global)",needsPermission("context.write"),context.delete,apiUtil.errorHandler);
|
||||
adminApp.delete("/context/:scope(global)/*",needsPermission("context.write"),context.delete,apiUtil.errorHandler);
|
||||
// adminApp.delete("/context/:scope(node|flow)/:id",needsPermission("context.write"),context.delete,apiUtil.errorHandler);
|
||||
adminApp.delete("/context/:scope(node|flow)/:id/*",needsPermission("context.write"),context.delete,apiUtil.errorHandler);
|
||||
|
||||
return adminApp;
|
||||
}
|
||||
}
|
||||
173
packages/node_modules/@node-red/editor-api/lib/admin/nodes.js
vendored
Normal file
173
packages/node_modules/@node-red/editor-api/lib/admin/nodes.js
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var apiUtils = require("../util");
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
getAll: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
if (req.get("accept") == "application/json") {
|
||||
runtimeAPI.nodes.getNodeList(opts).then(function(list) {
|
||||
res.json(list);
|
||||
})
|
||||
} else {
|
||||
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
runtimeAPI.nodes.getNodeConfigs(opts).then(function(configs) {
|
||||
res.send(configs);
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
post: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.body.module,
|
||||
version: req.body.version
|
||||
}
|
||||
runtimeAPI.nodes.addModule(opts).then(function(info) {
|
||||
res.json(info);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
delete: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.params[0]
|
||||
}
|
||||
runtimeAPI.nodes.removeModule(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
getSet: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params[0] + "/" + req.params[2]
|
||||
}
|
||||
if (req.get("accept") === "application/json") {
|
||||
runtimeAPI.nodes.getNodeInfo(opts).then(function(result) {
|
||||
res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
} else {
|
||||
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
runtimeAPI.nodes.getNodeConfig(opts).then(function(result) {
|
||||
return res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getModule: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.params[0]
|
||||
}
|
||||
runtimeAPI.nodes.getModuleInfo(opts).then(function(result) {
|
||||
res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
putSet: function(req,res) {
|
||||
var body = req.body;
|
||||
if (!body.hasOwnProperty("enabled")) {
|
||||
// log.audit({event: "nodes.module.set",error:"invalid_request"},req);
|
||||
res.status(400).json({code:"invalid_request", message:"Invalid request"});
|
||||
return;
|
||||
}
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params[0] + "/" + req.params[2],
|
||||
enabled: body.enabled
|
||||
}
|
||||
runtimeAPI.nodes.setNodeSetState(opts).then(function(result) {
|
||||
res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
putModule: function(req,res) {
|
||||
var body = req.body;
|
||||
if (!body.hasOwnProperty("enabled")) {
|
||||
// log.audit({event: "nodes.module.set",error:"invalid_request"},req);
|
||||
res.status(400).json({code:"invalid_request", message:"Invalid request"});
|
||||
return;
|
||||
}
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.params[0],
|
||||
enabled: body.enabled
|
||||
}
|
||||
runtimeAPI.nodes.setModuleState(opts).then(function(result) {
|
||||
res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
getModuleCatalog: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.params[0],
|
||||
lang: req.query.lng
|
||||
}
|
||||
runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
getModuleCatalogs: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
lang: req.query.lng
|
||||
}
|
||||
runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
getIcons: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
runtimeAPI.nodes.getIconList(opts).then(function(list) {
|
||||
res.json(list);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,8 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var when = require("when");
|
||||
|
||||
var clients = [
|
||||
{id:"node-red-editor",secret:"not_available"},
|
||||
@@ -25,9 +23,9 @@ module.exports = {
|
||||
get: function(id) {
|
||||
for (var i=0;i<clients.length;i++) {
|
||||
if (clients[i].id == id) {
|
||||
return when.resolve(clients[i]);
|
||||
return Promise.resolve(clients[i]);
|
||||
}
|
||||
}
|
||||
return when.resolve(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
218
packages/node_modules/@node-red/editor-api/lib/auth/index.js
vendored
Normal file
218
packages/node_modules/@node-red/editor-api/lib/auth/index.js
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var passport = require("passport");
|
||||
var oauth2orize = require("oauth2orize");
|
||||
|
||||
var strategies = require("./strategies");
|
||||
var Tokens = require("./tokens");
|
||||
var Users = require("./users");
|
||||
var permissions = require("./permissions");
|
||||
|
||||
var theme = require("../editor/theme");
|
||||
|
||||
var settings = null;
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
|
||||
passport.use(strategies.bearerStrategy.BearerStrategy);
|
||||
passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy);
|
||||
passport.use(strategies.anonymousStrategy);
|
||||
|
||||
var server = oauth2orize.createServer();
|
||||
|
||||
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
|
||||
|
||||
function init(_settings,storage) {
|
||||
settings = _settings;
|
||||
if (settings.adminAuth) {
|
||||
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
|
||||
Users.init(mergedAdminAuth);
|
||||
Tokens.init(mergedAdminAuth,storage);
|
||||
}
|
||||
}
|
||||
|
||||
function needsPermission(permission) {
|
||||
return function(req,res,next) {
|
||||
if (settings && settings.adminAuth) {
|
||||
return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() {
|
||||
if (!req.user) {
|
||||
return next();
|
||||
}
|
||||
if (permissions.hasPermission(req.authInfo.scope,permission)) {
|
||||
return next();
|
||||
}
|
||||
log.audit({event: "permission.fail", permissions: permission},req);
|
||||
return res.status(401).end();
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureClientSecret(req,res,next) {
|
||||
if (!req.body.client_secret) {
|
||||
req.body.client_secret = 'not_available';
|
||||
}
|
||||
next();
|
||||
}
|
||||
function authenticateClient(req,res,next) {
|
||||
return passport.authenticate(['oauth2-client-password'], {session: false})(req,res,next);
|
||||
}
|
||||
function getToken(req,res,next) {
|
||||
return server.token()(req,res,next);
|
||||
}
|
||||
|
||||
function login(req,res) {
|
||||
var response = {};
|
||||
if (settings.adminAuth) {
|
||||
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
|
||||
if (mergedAdminAuth.type === "credentials") {
|
||||
response = {
|
||||
"type":"credentials",
|
||||
"prompts":[{id:"username",type:"text",label:"user.username"},{id:"password",type:"password",label:"user.password"}]
|
||||
}
|
||||
} else if (mergedAdminAuth.type === "strategy") {
|
||||
|
||||
var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot;
|
||||
response = {
|
||||
"type":"strategy",
|
||||
"prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}]
|
||||
}
|
||||
if (mergedAdminAuth.strategy.icon) {
|
||||
response.prompts[0].icon = mergedAdminAuth.strategy.icon;
|
||||
}
|
||||
if (mergedAdminAuth.strategy.image) {
|
||||
response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image);
|
||||
}
|
||||
}
|
||||
if (theme.context().login && theme.context().login.image) {
|
||||
response.image = theme.context().login.image;
|
||||
}
|
||||
}
|
||||
res.json(response);
|
||||
}
|
||||
|
||||
function revoke(req,res) {
|
||||
var token = req.body.token;
|
||||
// TODO: audit log
|
||||
Tokens.revoke(token).then(function() {
|
||||
log.audit({event: "auth.login.revoke"},req);
|
||||
if (settings.editorTheme && settings.editorTheme.logout && settings.editorTheme.logout.redirect) {
|
||||
res.json({redirect:settings.editorTheme.logout.redirect});
|
||||
} else {
|
||||
res.status(200).end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function completeVerify(profile,done) {
|
||||
Users.authenticate(profile).then(function(user) {
|
||||
if (user) {
|
||||
Tokens.create(user.username,"node-red-editor",user.permissions).then(function(tokens) {
|
||||
log.audit({event: "auth.login",username:user.username,scope:user.permissions});
|
||||
user.tokens = tokens;
|
||||
done(null,user);
|
||||
});
|
||||
} else {
|
||||
log.audit({event: "auth.login.fail.oauth",username:typeof profile === "string"?profile:profile.username});
|
||||
done(null,false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function genericStrategy(adminApp,strategy) {
|
||||
var crypto = require("crypto")
|
||||
var session = require('express-session')
|
||||
var MemoryStore = require('memorystore')(session)
|
||||
|
||||
adminApp.use(session({
|
||||
// As the session is only used across the life-span of an auth
|
||||
// hand-shake, we can use a instance specific random string
|
||||
secret: crypto.randomBytes(20).toString('hex'),
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: new MemoryStore({
|
||||
checkPeriod: 86400000 // prune expired entries every 24h
|
||||
})
|
||||
}));
|
||||
//TODO: all passport references ought to be in ./auth
|
||||
adminApp.use(passport.initialize());
|
||||
adminApp.use(passport.session());
|
||||
|
||||
var options = strategy.options;
|
||||
|
||||
passport.use(new strategy.strategy(options,
|
||||
function() {
|
||||
var originalDone = arguments[arguments.length-1];
|
||||
if (options.verify) {
|
||||
var args = Array.from(arguments);
|
||||
args[args.length-1] = function(err,profile) {
|
||||
if (err) {
|
||||
return originalDone(err);
|
||||
} else {
|
||||
return completeVerify(profile,originalDone);
|
||||
}
|
||||
};
|
||||
options.verify.apply(null,args);
|
||||
} else {
|
||||
var profile = arguments[arguments.length - 2];
|
||||
return completeVerify(profile,originalDone);
|
||||
}
|
||||
|
||||
}
|
||||
));
|
||||
|
||||
adminApp.get('/auth/strategy',
|
||||
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
|
||||
completeGenerateStrategyAuth
|
||||
);
|
||||
|
||||
var callbackMethodFunc = adminApp.get;
|
||||
if (/^post$/i.test(options.callbackMethod)) {
|
||||
callbackMethodFunc = adminApp.post;
|
||||
}
|
||||
callbackMethodFunc('/auth/strategy/callback',
|
||||
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
|
||||
completeGenerateStrategyAuth
|
||||
);
|
||||
|
||||
}
|
||||
function completeGenerateStrategyAuth(req,res) {
|
||||
var tokens = req.user.tokens;
|
||||
delete req.user.tokens;
|
||||
// Successful authentication, redirect home.
|
||||
res.redirect(settings.httpAdminRoot + '?access_token='+tokens.accessToken);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
needsPermission: needsPermission,
|
||||
ensureClientSecret: ensureClientSecret,
|
||||
authenticateClient: authenticateClient,
|
||||
getToken: getToken,
|
||||
errorHandler: function(err,req,res,next) {
|
||||
//TODO: audit log statment
|
||||
//console.log(err.stack);
|
||||
//log.log({level:"audit",type:"auth",msg:err.toString()});
|
||||
return server.errorHandler()(err,req,res,next);
|
||||
},
|
||||
login: login,
|
||||
revoke: revoke,
|
||||
genericStrategy: genericStrategy
|
||||
}
|
||||
65
packages/node_modules/@node-red/editor-api/lib/auth/permissions.js
vendored
Normal file
65
packages/node_modules/@node-red/editor-api/lib/auth/permissions.js
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var util = require('util');
|
||||
|
||||
var readRE = /^((.+)\.)?read$/
|
||||
var writeRE = /^((.+)\.)?write$/
|
||||
|
||||
function hasPermission(userScope,permission) {
|
||||
if (permission === "") {
|
||||
return true;
|
||||
}
|
||||
var i;
|
||||
|
||||
if (util.isArray(permission)) {
|
||||
// Multiple permissions requested - check each one
|
||||
for (i=0;i<permission.length;i++) {
|
||||
if (!hasPermission(userScope,permission[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// All permissions check out
|
||||
return true;
|
||||
}
|
||||
|
||||
if (util.isArray(userScope)) {
|
||||
if (userScope.length === 0) {
|
||||
return false;
|
||||
}
|
||||
for (i=0;i<userScope.length;i++) {
|
||||
if (hasPermission(userScope[i],permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (userScope === "*" || userScope === permission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (userScope === "read" || userScope === "*.read") {
|
||||
return readRE.test(permission);
|
||||
} else if (userScope === "write" || userScope === "*.write") {
|
||||
return writeRE.test(permission);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hasPermission: hasPermission,
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,9 @@ var util = require("util");
|
||||
var Tokens = require("./tokens");
|
||||
var Users = require("./users");
|
||||
var Clients = require("./clients");
|
||||
var permissions = require("./permissions");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
var bearerStrategy = function (accessToken, done) {
|
||||
// is this a valid token?
|
||||
@@ -33,10 +36,12 @@ var bearerStrategy = function (accessToken, done) {
|
||||
if (user) {
|
||||
done(null,user,{scope:token.scope});
|
||||
} else {
|
||||
log.audit({event: "auth.invalid-token"});
|
||||
done(null,false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.audit({event: "auth.invalid-token"});
|
||||
done(null,false);
|
||||
}
|
||||
});
|
||||
@@ -48,6 +53,7 @@ var clientPasswordStrategy = function(clientId, clientSecret, done) {
|
||||
if (client && client.secret == clientSecret) {
|
||||
done(null,client);
|
||||
} else {
|
||||
log.audit({event: "auth.invalid-client",client:clientId});
|
||||
done(null,false);
|
||||
}
|
||||
});
|
||||
@@ -55,38 +61,47 @@ var clientPasswordStrategy = function(clientId, clientSecret, done) {
|
||||
clientPasswordStrategy.ClientPasswordStrategy = new ClientPasswordStrategy(clientPasswordStrategy);
|
||||
|
||||
var loginAttempts = [];
|
||||
var loginSignUpWindow = 36000000; // 10 minutes
|
||||
var loginSignInWindow = 600000; // 10 minutes
|
||||
|
||||
|
||||
var passwordTokenExchange = function(client, username, password, scope, done) {
|
||||
var now = Date.now();
|
||||
loginAttempts = loginAttempts.filter(function(logEntry) {
|
||||
return logEntry.time + loginSignUpWindow > now;
|
||||
return logEntry.time + loginSignInWindow > now;
|
||||
});
|
||||
loginAttempts.push({time:now, user:username});
|
||||
var attemptCount = 0;
|
||||
loginAttempts.forEach(function(logEntry) {
|
||||
/* istanbul ignore else */
|
||||
if (logEntry.user == username) {
|
||||
attemptCount++;
|
||||
}
|
||||
});
|
||||
if (attemptCount > 5) {
|
||||
// TODO: audit log
|
||||
log.audit({event: "auth.login.fail.too-many-attempts",username:username,client:client.id});
|
||||
done(new Error("Too many login attempts. Wait 10 minutes and try again"),false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Users.authenticate(username,password).then(function(user) {
|
||||
if (user) {
|
||||
loginAttempts = loginAttempts.filter(function(logEntry) {
|
||||
return logEntry.user !== username;
|
||||
});
|
||||
Tokens.create(username,client.id,scope).then(function(tokens) {
|
||||
// TODO: audit log
|
||||
done(null,tokens.accessToken);
|
||||
});
|
||||
if (scope === "") {
|
||||
scope = user.permissions;
|
||||
}
|
||||
if (permissions.hasPermission(user.permissions,scope)) {
|
||||
loginAttempts = loginAttempts.filter(function(logEntry) {
|
||||
return logEntry.user !== username;
|
||||
});
|
||||
Tokens.create(username,client.id,scope).then(function(tokens) {
|
||||
log.audit({event: "auth.login",username:username,client:client.id,scope:scope});
|
||||
done(null,tokens.accessToken,null,{expires_in:tokens.expires_in});
|
||||
});
|
||||
} else {
|
||||
log.audit({event: "auth.login.fail.permissions",username:username,client:client.id,scope:scope});
|
||||
done(null,false);
|
||||
}
|
||||
} else {
|
||||
// TODO: audit log
|
||||
log.audit({event: "auth.login.fail.credentials",username:username,client:client.id,scope:scope});
|
||||
done(null,false);
|
||||
}
|
||||
});
|
||||
@@ -101,7 +116,7 @@ AnonymousStrategy.prototype.authenticate = function(req) {
|
||||
var self = this;
|
||||
Users.default().then(function(anon) {
|
||||
if (anon) {
|
||||
self.success(anon);
|
||||
self.success(anon,{scope:anon.permissions});
|
||||
} else {
|
||||
self.fail(401);
|
||||
}
|
||||
152
packages/node_modules/@node-red/editor-api/lib/auth/tokens.js
vendored
Normal file
152
packages/node_modules/@node-red/editor-api/lib/auth/tokens.js
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
function generateToken(length) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var token = [];
|
||||
for (var i=0;i<length;i++) {
|
||||
token.push(c[Math.floor(Math.random()*c.length)]);
|
||||
}
|
||||
return token.join("");
|
||||
}
|
||||
|
||||
|
||||
var storage;
|
||||
var sessionExpiryTime
|
||||
var sessions = {};
|
||||
var loadedSessions = null;
|
||||
var apiAccessTokens;
|
||||
var sessionExpiryListeners = [];
|
||||
var expiryTimeout;
|
||||
|
||||
function expireSessions() {
|
||||
if (expiryTimeout) {
|
||||
clearTimeout(expiryTimeout);
|
||||
expiryTimeout = null;
|
||||
}
|
||||
var nextExpiry = Number.MAX_SAFE_INTEGER;
|
||||
var now = Date.now();
|
||||
var modified = false;
|
||||
for (var t in sessions) {
|
||||
if (sessions.hasOwnProperty(t)) {
|
||||
var session = sessions[t];
|
||||
if (!session.hasOwnProperty("expires") || session.expires < now) {
|
||||
sessionExpiryListeners.forEach(listener => { listener(session) })
|
||||
delete sessions[t];
|
||||
modified = true;
|
||||
} else {
|
||||
if (session.expires < nextExpiry) {
|
||||
nextExpiry = session.expires;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextExpiry < Number.MAX_SAFE_INTEGER) {
|
||||
// Allow 5 seconds grace
|
||||
expiryTimeout = setTimeout(expireSessions,(nextExpiry - Date.now()) + 5000)
|
||||
}
|
||||
if (modified) {
|
||||
return storage.saveSessions(sessions);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
function loadSessions() {
|
||||
if (loadedSessions === null) {
|
||||
loadedSessions = storage.getSessions().then(function(_sessions) {
|
||||
sessions = _sessions||{};
|
||||
return expireSessions();
|
||||
});
|
||||
}
|
||||
return loadedSessions;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(adminAuthSettings, _storage) {
|
||||
storage = _storage;
|
||||
|
||||
sessionExpiryListeners = [];
|
||||
|
||||
sessionExpiryTime = adminAuthSettings.sessionExpiryTime || 604800; // 1 week in seconds
|
||||
// At this point, storage will not have been initialised, so defer loading
|
||||
// the sessions until there's a request for them.
|
||||
loadedSessions = null;
|
||||
|
||||
apiAccessTokens = {};
|
||||
if ( Array.isArray(adminAuthSettings.tokens) ) {
|
||||
apiAccessTokens = adminAuthSettings.tokens.reduce(function(prev, current) {
|
||||
prev[current.token] = {
|
||||
user: current.user,
|
||||
scope: current.scope
|
||||
};
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
get: function(token) {
|
||||
return loadSessions().then(function() {
|
||||
var info = apiAccessTokens[token] || null;
|
||||
|
||||
if (info) {
|
||||
return Promise.resolve(info);
|
||||
} else {
|
||||
if (sessions[token]) {
|
||||
if (sessions[token].expires < Date.now()) {
|
||||
return expireSessions().then(function() { return null });
|
||||
}
|
||||
}
|
||||
return Promise.resolve(sessions[token]);
|
||||
}
|
||||
});
|
||||
},
|
||||
create: function(user,client,scope) {
|
||||
return loadSessions().then(function() {
|
||||
var accessToken = generateToken(128);
|
||||
|
||||
var accessTokenExpiresAt = Date.now() + (sessionExpiryTime*1000);
|
||||
|
||||
var session = {
|
||||
user:user,
|
||||
client:client,
|
||||
scope:scope,
|
||||
accessToken: accessToken,
|
||||
expires: accessTokenExpiresAt
|
||||
};
|
||||
sessions[accessToken] = session;
|
||||
|
||||
if (!expiryTimeout) {
|
||||
expiryTimeout = setTimeout(expireSessions,(accessTokenExpiresAt - Date.now()) + 5000)
|
||||
}
|
||||
|
||||
return storage.saveSessions(sessions).then(function() {
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
expires_in: sessionExpiryTime
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
revoke: function(token) {
|
||||
return loadSessions().then(function() {
|
||||
delete sessions[token];
|
||||
return storage.saveSessions(sessions);
|
||||
});
|
||||
},
|
||||
onSessionExpiry: function(callback) {
|
||||
sessionExpiryListeners.push(callback);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,30 +14,46 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var when = require("when");
|
||||
var util = require("util");
|
||||
var bcrypt = require('bcryptjs');
|
||||
|
||||
var clone = require("clone");
|
||||
var bcrypt;
|
||||
try { bcrypt = require('bcrypt'); }
|
||||
catch(e) { bcrypt = require('bcryptjs'); }
|
||||
var users = {};
|
||||
var passwords = {};
|
||||
var defaultUser = null;
|
||||
|
||||
function authenticate(username,password) {
|
||||
var user = users[username];
|
||||
if (user) {
|
||||
return when.promise(function(resolve,reject) {
|
||||
bcrypt.compare(password, passwords[username], function(err, res) {
|
||||
resolve(res?user:null);
|
||||
});
|
||||
});
|
||||
function authenticate() {
|
||||
var username = arguments[0];
|
||||
if (typeof username !== 'string') {
|
||||
username = username.username;
|
||||
}
|
||||
return when.resolve(null);
|
||||
const args = Array.from(arguments);
|
||||
return api.get(username).then(function(user) {
|
||||
if (user) {
|
||||
if (args.length === 2) {
|
||||
// Username/password authentication
|
||||
var password = args[1];
|
||||
return new Promise(function(resolve,reject) {
|
||||
bcrypt.compare(password, user.password, function(err, res) {
|
||||
resolve(res?cleanUser(user):null);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Try to extract common profile information
|
||||
if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) {
|
||||
user.image = args[0].photos[0].value;
|
||||
}
|
||||
return cleanUser(user);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
function get(username) {
|
||||
return when.resolve(users[username]);
|
||||
return Promise.resolve(users[username]);
|
||||
}
|
||||
function getDefaultUser() {
|
||||
return when.resolve(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
var api = {
|
||||
@@ -48,24 +64,20 @@ var api = {
|
||||
|
||||
function init(config) {
|
||||
users = {};
|
||||
passwords = {};
|
||||
defaultUser = null;
|
||||
if (config.type == "credentials") {
|
||||
if (config.type == "credentials" || config.type == "strategy") {
|
||||
if (config.users) {
|
||||
if (typeof config.users === "function") {
|
||||
api.get = config.users;
|
||||
} else {
|
||||
var us = config.users;
|
||||
/* istanbul ignore else */
|
||||
if (!util.isArray(us)) {
|
||||
us = [us];
|
||||
}
|
||||
for (var i=0;i<us.length;i++) {
|
||||
var u = us[i];
|
||||
users[u.username] = {
|
||||
"username":u.username,
|
||||
"permissions":u.permissions
|
||||
};
|
||||
passwords[u.username] = u.password;
|
||||
users[u.username] = clone(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,13 +86,17 @@ function init(config) {
|
||||
} else {
|
||||
api.authenticate = authenticate;
|
||||
}
|
||||
} else {
|
||||
api.get = get;
|
||||
api.authenticate = authenticate;
|
||||
api.default = api.default;
|
||||
}
|
||||
if (config.default) {
|
||||
if (typeof config.default === "function") {
|
||||
api.default = config.default;
|
||||
} else {
|
||||
api.default = function() {
|
||||
return when.resolve({
|
||||
return Promise.resolve({
|
||||
"anonymous": true,
|
||||
"permissions":config.default.permissions
|
||||
});
|
||||
@@ -90,12 +106,17 @@ function init(config) {
|
||||
api.default = getDefaultUser;
|
||||
}
|
||||
}
|
||||
function cleanUser(user) {
|
||||
if (user && user.hasOwnProperty('password')) {
|
||||
user = clone(user);
|
||||
delete user.password;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
get: function(username) { return api.get(username) },
|
||||
authenticate: function(username,password) { return api.authenticate(username,password) },
|
||||
get: function(username) { return api.get(username).then(cleanUser)},
|
||||
authenticate: function() { return api.authenticate.apply(null, arguments) },
|
||||
default: function() { return api.default(); }
|
||||
};
|
||||
|
||||
|
||||
253
packages/node_modules/@node-red/editor-api/lib/editor/comms.js
vendored
Normal file
253
packages/node_modules/@node-red/editor-api/lib/editor/comms.js
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var ws = require("ws");
|
||||
var url = require("url");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
var Tokens;
|
||||
var Users;
|
||||
var Permissions;
|
||||
|
||||
var server;
|
||||
var settings;
|
||||
var runtimeAPI;
|
||||
|
||||
var wsServer;
|
||||
var activeConnections = [];
|
||||
|
||||
var anonymousUser;
|
||||
|
||||
var retained = {};
|
||||
|
||||
var heartbeatTimer;
|
||||
var lastSentTime;
|
||||
|
||||
function init(_server,_settings,_runtimeAPI) {
|
||||
server = _server;
|
||||
settings = _settings;
|
||||
runtimeAPI = _runtimeAPI;
|
||||
Tokens = require("../auth/tokens");
|
||||
Tokens.onSessionExpiry(handleSessionExpiry);
|
||||
Users = require("../auth/users");
|
||||
Permissions = require("../auth/permissions");
|
||||
|
||||
}
|
||||
function handleSessionExpiry(session) {
|
||||
activeConnections.forEach(connection => {
|
||||
if (connection.token === session.accessToken) {
|
||||
connection.ws.send(JSON.stringify({auth:"fail"}));
|
||||
connection.ws.close();
|
||||
}
|
||||
})
|
||||
}
|
||||
function generateSession(length) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var token = [];
|
||||
for (var i=0;i<length;i++) {
|
||||
token.push(c[Math.floor(Math.random()*c.length)]);
|
||||
}
|
||||
return token.join("");
|
||||
}
|
||||
|
||||
function CommsConnection(ws) {
|
||||
this.session = generateSession(32);
|
||||
this.ws = ws;
|
||||
this.stack = [];
|
||||
this.user = null;
|
||||
this.lastSentTime = 0;
|
||||
var self = this;
|
||||
|
||||
log.audit({event: "comms.open"});
|
||||
log.trace("comms.open "+self.session);
|
||||
var pendingAuth = (settings.adminAuth != null);
|
||||
|
||||
if (!pendingAuth) {
|
||||
addActiveConnection(self);
|
||||
}
|
||||
ws.on('close',function() {
|
||||
log.audit({event: "comms.close",user:self.user, session: self.session});
|
||||
log.trace("comms.close "+self.session);
|
||||
removeActiveConnection(self);
|
||||
});
|
||||
ws.on('message', function(data,flags) {
|
||||
var msg = null;
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
} catch(err) {
|
||||
log.trace("comms received malformed message : "+err.toString());
|
||||
return;
|
||||
}
|
||||
if (!pendingAuth) {
|
||||
if (msg.subscribe) {
|
||||
self.subscribe(msg.subscribe);
|
||||
// handleRemoteSubscription(ws,msg.subscribe);
|
||||
}
|
||||
} else {
|
||||
var completeConnection = function(userScope,session,sendAck) {
|
||||
try {
|
||||
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
|
||||
ws.send(JSON.stringify({auth:"fail"}));
|
||||
ws.close();
|
||||
} else {
|
||||
pendingAuth = false;
|
||||
addActiveConnection(self);
|
||||
self.token = msg.auth;
|
||||
if (sendAck) {
|
||||
ws.send(JSON.stringify({auth:"ok"}));
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
console.log(err.stack);
|
||||
// Just in case the socket closes before we attempt
|
||||
// to send anything.
|
||||
}
|
||||
}
|
||||
if (msg.auth) {
|
||||
Tokens.get(msg.auth).then(function(client) {
|
||||
if (client) {
|
||||
Users.get(client.user).then(function(user) {
|
||||
if (user) {
|
||||
self.user = user;
|
||||
log.audit({event: "comms.auth",user:self.user});
|
||||
completeConnection(client.scope,msg.auth,true);
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,null,false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,null,false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (anonymousUser) {
|
||||
log.audit({event: "comms.auth",user:anonymousUser});
|
||||
self.user = anonymousUser;
|
||||
completeConnection(anonymousUser.permissions,null,false);
|
||||
//TODO: duplicated code - pull non-auth message handling out
|
||||
if (msg.subscribe) {
|
||||
self.subscribe(msg.subscribe);
|
||||
}
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,null,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ws.on('error', function(err) {
|
||||
log.warn(log._("comms.error",{message:err.toString()}));
|
||||
});
|
||||
}
|
||||
|
||||
CommsConnection.prototype.send = function(topic,data) {
|
||||
var self = this;
|
||||
if (topic && data) {
|
||||
this.stack.push({topic:topic,data:data});
|
||||
}
|
||||
if (!this._xmitTimer) {
|
||||
this._xmitTimer = setTimeout(function() {
|
||||
try {
|
||||
self.ws.send(JSON.stringify(self.stack));
|
||||
self.lastSentTime = Date.now();
|
||||
} catch(err) {
|
||||
removeActiveConnection(self);
|
||||
log.warn(log._("comms.error-send",{message:err.toString()}));
|
||||
}
|
||||
delete self._xmitTimer;
|
||||
self.stack = [];
|
||||
},50);
|
||||
}
|
||||
}
|
||||
|
||||
CommsConnection.prototype.subscribe = function(topic) {
|
||||
runtimeAPI.comms.subscribe({
|
||||
user: this.user,
|
||||
client: this,
|
||||
topic: topic
|
||||
})
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (!settings.disableEditor) {
|
||||
Users.default().then(function(_anonymousUser) {
|
||||
anonymousUser = _anonymousUser;
|
||||
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
|
||||
var commsPath = settings.httpAdminRoot || "/";
|
||||
commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms";
|
||||
wsServer = new ws.Server({ noServer: true });
|
||||
wsServer.on('connection',function(ws) {
|
||||
var commsConnection = new CommsConnection(ws);
|
||||
});
|
||||
wsServer.on('error', function(err) {
|
||||
log.warn(log._("comms.error-server",{message:err.toString()}));
|
||||
});
|
||||
|
||||
server.on('upgrade', function upgrade(request, socket, head) {
|
||||
const pathname = url.parse(request.url).pathname;
|
||||
if (pathname === commsPath) {
|
||||
wsServer.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wsServer.emit('connection', ws, request);
|
||||
});
|
||||
}
|
||||
// Don't destroy the socket as other listeners may want to handle the
|
||||
// event.
|
||||
});
|
||||
|
||||
lastSentTime = Date.now();
|
||||
|
||||
heartbeatTimer = setInterval(function() {
|
||||
var now = Date.now();
|
||||
if (now-lastSentTime > webSocketKeepAliveTime) {
|
||||
activeConnections.forEach(connection => connection.send("hb",lastSentTime));
|
||||
}
|
||||
}, webSocketKeepAliveTime);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (heartbeatTimer) {
|
||||
clearInterval(heartbeatTimer);
|
||||
heartbeatTimer = null;
|
||||
}
|
||||
if (wsServer) {
|
||||
wsServer.close();
|
||||
wsServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function addActiveConnection(connection) {
|
||||
activeConnections.push(connection);
|
||||
runtimeAPI.comms.addConnection({client: connection});
|
||||
}
|
||||
function removeActiveConnection(connection) {
|
||||
for (var i=0;i<activeConnections.length;i++) {
|
||||
if (activeConnections[i] === connection) {
|
||||
activeConnections.splice(i,1);
|
||||
runtimeAPI.comms.removeConnection({client:connection})
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init:init,
|
||||
start:start,
|
||||
stop:stop
|
||||
}
|
||||
36
packages/node_modules/@node-red/editor-api/lib/editor/credentials.js
vendored
Normal file
36
packages/node_modules/@node-red/editor-api/lib/editor/credentials.js
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var runtimeAPI;
|
||||
var apiUtils = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI
|
||||
},
|
||||
get: function (req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
type: req.params.type,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.flows.getNodeCredentials(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
||||
129
packages/node_modules/@node-red/editor-api/lib/editor/index.js
vendored
Normal file
129
packages/node_modules/@node-red/editor-api/lib/editor/index.js
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var express = require("express");
|
||||
var path = require('path');
|
||||
|
||||
var comms = require("./comms");
|
||||
var library = require("./library");
|
||||
var info = require("./settings");
|
||||
|
||||
var auth = require("../auth");
|
||||
var nodes = require("../admin/nodes"); // TODO: move /icons into here
|
||||
var needsPermission;
|
||||
var runtimeAPI;
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
var i18n = require("@node-red/util").i18n; // TODO: separate module
|
||||
|
||||
var apiUtil = require("../util");
|
||||
|
||||
var ensureRuntimeStarted = function(req,res,next) {
|
||||
runtimeAPI.isStarted().then( started => {
|
||||
if (!started) {
|
||||
log.error("Node-RED runtime not started");
|
||||
res.status(503).send("Not started");
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(server, settings, _runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
needsPermission = auth.needsPermission;
|
||||
if (!settings.disableEditor) {
|
||||
info.init(runtimeAPI);
|
||||
comms.init(server,settings,runtimeAPI);
|
||||
|
||||
var ui = require("./ui");
|
||||
|
||||
ui.init(runtimeAPI);
|
||||
|
||||
var editorApp = express();
|
||||
if (settings.requireHttps === true) {
|
||||
editorApp.enable('trust proxy');
|
||||
editorApp.use(function (req, res, next) {
|
||||
if (req.secure) {
|
||||
next();
|
||||
} else {
|
||||
res.redirect('https://' + req.headers.host + req.originalUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (settings.httpServerOptions) {
|
||||
for (var eOption in settings.httpServerOptions) {
|
||||
editorApp.set(eOption, settings.httpServerOptions[eOption]);
|
||||
}
|
||||
}
|
||||
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
||||
|
||||
editorApp.get("/icons",needsPermission("nodes.read"),nodes.getIcons,apiUtil.errorHandler);
|
||||
editorApp.get("/icons/:module/:icon",ui.icon);
|
||||
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
|
||||
|
||||
var theme = require("./theme");
|
||||
theme.init(settings);
|
||||
editorApp.use("/theme",theme.app());
|
||||
editorApp.use("/",ui.editorResources);
|
||||
|
||||
//Projects
|
||||
var projects = require("./projects");
|
||||
projects.init(runtimeAPI);
|
||||
editorApp.use("/projects",projects.app());
|
||||
|
||||
// Locales
|
||||
var locales = require("./locales");
|
||||
locales.init(runtimeAPI);
|
||||
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
|
||||
|
||||
// Library
|
||||
var library = require("./library");
|
||||
library.init(runtimeAPI);
|
||||
editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler);
|
||||
editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
|
||||
editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
|
||||
|
||||
|
||||
// Credentials
|
||||
var credentials = require("./credentials");
|
||||
credentials.init(runtimeAPI);
|
||||
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
|
||||
|
||||
// Settings
|
||||
editorApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
|
||||
// User Settings
|
||||
editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler);
|
||||
// User Settings
|
||||
editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler);
|
||||
// SSH keys
|
||||
editorApp.use("/settings/user/keys",needsPermission("settings.write"),info.sshkeys());
|
||||
|
||||
return editorApp;
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
var catalogPath = path.resolve(path.join(path.dirname(require.resolve("@node-red/editor-client")),"locales"));
|
||||
return i18n.registerMessageCatalogs([
|
||||
{namespace: "editor", dir: catalogPath, file:"editor.json"},
|
||||
{namespace: "jsonata", dir: catalogPath, file:"jsonata.json"},
|
||||
{namespace: "infotips", dir: catalogPath, file:"infotips.json"}
|
||||
]).then(function(){
|
||||
comms.start();
|
||||
});
|
||||
},
|
||||
stop: comms.stop
|
||||
}
|
||||
83
packages/node_modules/@node-red/editor-api/lib/editor/library.js
vendored
Normal file
83
packages/node_modules/@node-red/editor-api/lib/editor/library.js
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var apiUtils = require("../util");
|
||||
var fs = require('fs');
|
||||
var fspath = require('path');
|
||||
var when = require('when');
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
|
||||
getAll: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
type: 'flows'
|
||||
}
|
||||
runtimeAPI.library.getEntries(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
},
|
||||
getEntry: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
type: req.params[0],
|
||||
path: req.params[1]||""
|
||||
}
|
||||
runtimeAPI.library.getEntry(opts).then(function(result) {
|
||||
if (typeof result === "string") {
|
||||
if (opts.type === 'flows') {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
} else {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
}
|
||||
res.write(result);
|
||||
res.end();
|
||||
} else {
|
||||
res.json(result);
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
},
|
||||
saveEntry: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
type: req.params[0],
|
||||
path: req.params[1]||""
|
||||
}
|
||||
// TODO: horrible inconsistencies between flows and all other types
|
||||
if (opts.type === "flows") {
|
||||
opts.meta = {};
|
||||
opts.body = JSON.stringify(req.body);
|
||||
} else {
|
||||
opts.meta = req.body;
|
||||
opts.body = opts.meta.text;
|
||||
delete opts.meta.text;
|
||||
}
|
||||
runtimeAPI.library.saveEntry(opts).then(function(result) {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
}
|
||||
}
|
||||
53
packages/node_modules/@node-red/editor-api/lib/editor/locales.js
vendored
Normal file
53
packages/node_modules/@node-red/editor-api/lib/editor/locales.js
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
//var apiUtil = require('../util');
|
||||
|
||||
var i18n = require("@node-red/util").i18n; // TODO: separate module
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
function loadResource(lang, namespace) {
|
||||
var catalog = i18n.i.getResourceBundle(lang, namespace);
|
||||
if (!catalog) {
|
||||
var parts = lang.split("-");
|
||||
if (parts.length == 2) {
|
||||
var new_lang = parts[0];
|
||||
return i18n.i.getResourceBundle(new_lang, namespace);
|
||||
}
|
||||
}
|
||||
return catalog;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
get: function(req,res) {
|
||||
var namespace = req.params[0];
|
||||
var lngs = req.query.lng;
|
||||
namespace = namespace.replace(/\.json$/,"");
|
||||
var lang = req.query.lng; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
|
||||
var prevLang = i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
i18n.i.changeLanguage(lang, function(){
|
||||
var catalog = loadResource(lang, namespace);
|
||||
res.json(catalog||{});
|
||||
});
|
||||
i18n.i.changeLanguage(prevLang);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user