├── Procfile ├── admin ├── img │ ├── dot-bg.png │ ├── lab-logo.png │ ├── node-open.png │ ├── design │ │ ├── focus.png │ │ ├── item type formatting.jpg │ │ ├── 2012-09-10 Comp-Cnotop.png │ │ ├── Spacebrew_0005_Click A.jpg │ │ ├── Spacebrew_0006_Click B.jpg │ │ ├── Spacebrew_0007_Click C.jpg │ │ ├── Spacebrew_0000_Default A.jpg │ │ ├── Spacebrew_0000_Default A notop.png │ │ ├── Spacebrew_0001_Receiving Input A.jpg │ │ ├── Spacebrew_0002_Receiving Input B.jpg │ │ ├── Spacebrew_0003_Hover Highlight A.jpg │ │ ├── Spacebrew_0004_Hover Highlight B.jpg │ │ └── Spacebrew_0000_Default A notop inv.png │ ├── node-closed.png │ ├── p-node-on-open.png │ ├── s-node-on-open.png │ ├── spacebrew-logo.png │ ├── p-node-on-closed.png │ ├── s-node-on-closed.png │ ├── spacebrew-logo-2.png │ ├── node-closed-active-i.png │ └── node-open-active-i.png ├── 2012-09-10 Comp-C.png ├── fonts │ └── DIN │ │ ├── DINPro-Bold.otf │ │ ├── DINPro-Black.otf │ │ ├── DINPro-Medium.otf │ │ ├── DINPro-Regular.otf │ │ ├── DINNeuzeitGroteskStd-Light.otf │ │ └── DINNeuzeitGroteskStd-BdCond.otf ├── css │ ├── reset.css │ └── style.css ├── js │ ├── load.js │ ├── jsplumb │ │ ├── jsPlumb-drag-1.3.13-RC1.js │ │ ├── jsPlumb-overlays-guidelines-1.3.13-RC1.js │ │ ├── jsPlumb-dom-adapter-1.3.13-RC1.js │ │ ├── jsPlumb-util-1.3.13-RC1.js │ │ ├── yui.jsPlumb-1.3.13-RC1.js │ │ ├── jquery.jsPlumb-1.3.13-RC1.js │ │ ├── mootools.jsPlumb-1.3.13-RC1.js │ │ └── jsPlumb-renderers-vml-1.3.13-RC1.js │ ├── plumbing.js │ ├── utils.js │ ├── main.js │ ├── userevents.js │ ├── wsevents.js │ └── require-2.0.4.js └── index.html ├── index.html ├── secure ├── README.md ├── server-key.crt ├── localhost.conf └── server-key.pem ├── .gitignore ├── logger.js ├── package.json ├── LICENSE.txt ├── schema.json ├── node_server_forever.js ├── node_persistent_live.js ├── node_server.js ├── README.md ├── http_link.js └── spacebrew_live_persist.js /Procfile: -------------------------------------------------------------------------------- 1 | web: node node_server_forever.js -p $PORT -------------------------------------------------------------------------------- /admin/img/dot-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/dot-bg.png -------------------------------------------------------------------------------- /admin/img/lab-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/lab-logo.png -------------------------------------------------------------------------------- /admin/img/node-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/node-open.png -------------------------------------------------------------------------------- /admin/img/design/focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/focus.png -------------------------------------------------------------------------------- /admin/img/node-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/node-closed.png -------------------------------------------------------------------------------- /admin/2012-09-10 Comp-C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/2012-09-10 Comp-C.png -------------------------------------------------------------------------------- /admin/img/p-node-on-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/p-node-on-open.png -------------------------------------------------------------------------------- /admin/img/s-node-on-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/s-node-on-open.png -------------------------------------------------------------------------------- /admin/img/spacebrew-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/spacebrew-logo.png -------------------------------------------------------------------------------- /admin/fonts/DIN/DINPro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/fonts/DIN/DINPro-Bold.otf -------------------------------------------------------------------------------- /admin/img/p-node-on-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/p-node-on-closed.png -------------------------------------------------------------------------------- /admin/img/s-node-on-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/s-node-on-closed.png -------------------------------------------------------------------------------- /admin/img/spacebrew-logo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/spacebrew-logo-2.png -------------------------------------------------------------------------------- /admin/fonts/DIN/DINPro-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/fonts/DIN/DINPro-Black.otf -------------------------------------------------------------------------------- /admin/fonts/DIN/DINPro-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/fonts/DIN/DINPro-Medium.otf -------------------------------------------------------------------------------- /admin/fonts/DIN/DINPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/fonts/DIN/DINPro-Regular.otf -------------------------------------------------------------------------------- /admin/img/node-closed-active-i.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/node-closed-active-i.png -------------------------------------------------------------------------------- /admin/img/node-open-active-i.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/node-open-active-i.png -------------------------------------------------------------------------------- /admin/img/design/item type formatting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/item type formatting.jpg -------------------------------------------------------------------------------- /admin/img/design/2012-09-10 Comp-Cnotop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/2012-09-10 Comp-Cnotop.png -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0005_Click A.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0005_Click A.jpg -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0006_Click B.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0006_Click B.jpg -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0007_Click C.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0007_Click C.jpg -------------------------------------------------------------------------------- /admin/fonts/DIN/DINNeuzeitGroteskStd-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/fonts/DIN/DINNeuzeitGroteskStd-Light.otf -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0000_Default A.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0000_Default A.jpg -------------------------------------------------------------------------------- /admin/fonts/DIN/DINNeuzeitGroteskStd-BdCond.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/fonts/DIN/DINNeuzeitGroteskStd-BdCond.otf -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0000_Default A notop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0000_Default A notop.png -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0001_Receiving Input A.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0001_Receiving Input A.jpg -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0002_Receiving Input B.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0002_Receiving Input B.jpg -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0003_Hover Highlight A.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0003_Hover Highlight A.jpg -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0004_Hover Highlight B.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0004_Hover Highlight B.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /admin/img/design/Spacebrew_0000_Default A notop inv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spacebrew/spacebrew/HEAD/admin/img/design/Spacebrew_0000_Default A notop inv.png -------------------------------------------------------------------------------- /admin/css/reset.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | border: 0px; 5 | padding: 0px; 6 | margin: 0px; 7 | background-color: gray; 8 | overflow:hidden; 9 | font-family: Helvetica; 10 | } 11 | html, body { 12 | /*cursor:none;*/ 13 | } -------------------------------------------------------------------------------- /secure/README.md: -------------------------------------------------------------------------------- 1 | This has been tested with Firefox. Firefox seems to not connect to wss using an expired certificate, but a self-signed cert is fine. 2 | 3 | to self-sign a cert, cd into this directory and run the following command (assuming you have openssl installed): 4 | 5 | `openssl req -config localhost.conf -new -sha256 -newkey rsa:2048 -nodes -x509 -days 365 -out server-key.crt` 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | .DS_Store 17 | 18 | #Xcode 19 | DerivedData 20 | *.build 21 | *.app 22 | *.xcworkspace 23 | *.xcuserdatad 24 | persistent_config.json 25 | data/* 26 | spacebrew_log* 27 | localhost.crt 28 | localhost.key 29 | video.expo.local.crt 30 | video.expo.local.key 31 | -------------------------------------------------------------------------------- /logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Logger Module 3 | * ------------- 4 | * 5 | * This module provides simple logging functionality used in most spacebrew scripts. 6 | * Developed based on an example from Josh Holbrook on the nodejitsu site. 7 | * 8 | * @author: Julio Terra 9 | * @filename: logger.js 10 | * @date: May 31, 2013 11 | * @updated with version: 0.3.0 12 | * 13 | */ 14 | var logger = exports; 15 | 16 | logger.debugLevel = 'debug'; 17 | 18 | logger.log = function(level, message) { 19 | var levels = ['error', 'warn', 'debug', 'info']; 20 | if (levels.indexOf(level) <= levels.indexOf(logger.debugLevel) ) { 21 | if (typeof message !== 'string') { 22 | message = JSON.stringify(message); 23 | }; 24 | console.log(level+': '+message); 25 | } 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spacebrew", 3 | "version": "0.4.0", 4 | "description": "A dynamically re-routable software toolkit for choreographing interactive spaces.", 5 | "main": "node_server_forever.js", 6 | "scripts": { 7 | "start": "node node_server_forever.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/spacebrew/spacebrew.git" 12 | }, 13 | "keywords": [ 14 | "spaces", 15 | "arduino", 16 | "serial", 17 | "websockets" 18 | ], 19 | "author": "LAB at Rockwell Group", 20 | "license": "MIT", 21 | "dependencies": { 22 | "ajv": "^3.8.3", 23 | "finalhandler": "^0.4.1", 24 | "forever-monitor": "1.1.0", 25 | "serve-static": "^1.10.2", 26 | "ws": "~0.4.25" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2012 LAB at Rockwell Group, http://www.rockwellgroup.com/lab 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /admin/js/load.js: -------------------------------------------------------------------------------- 1 | //including custom css-loading function 2 | //because requirejs does not support loading css 3 | function loadCSS(url){ 4 | var link = document.createElement("link"); 5 | link.type = "text/css"; 6 | link.rel = "stylesheet"; 7 | link.href = url; 8 | document.getElementsByTagName("head")[0].appendChild(link); 9 | return link; 10 | }; 11 | 12 | loadCSS("css/reset.css"); 13 | loadCSS("css/style.css"); 14 | 15 | require(["handlebars-1.0.0.beta.6", 16 | "jsplumb/jsPlumb-util-1.3.13-RC1", 17 | "jsplumb/jsPlumb-dom-adapter-1.3.13-RC1", 18 | "jquery/jquery-1.7.1-min"],function(){ 19 | //handlebars, jquery, jsplumbutil, jsplumbadapter is loaded 20 | require(["jsplumb/jsPlumb-1.3.13-RC1", 21 | "utils"],function(){ 22 | require([ 23 | "jsplumb/jsPlumb-renderers-svg-1.3.13-RC1"],function(){ 24 | //now jsPlumb is loaded 25 | require(["jsplumb/jquery.jsPlumb-1.3.13-RC1", 26 | "jsplumb/jsPlumb-connectors-statemachine-1.3.13-RC1", 27 | "jsplumb/jsPlumb-defaults-1.3.13-RC1", 28 | "jsplumb/jsPlumb-drag-1.3.13-RC1", 29 | "jsplumb/jsPlumb-overlays-guidelines-1.3.13-RC1"/*, 30 | //"jsplumb/jsPlumb-renderers-canvas-1.3.13-RC1",/*, 31 | "jsplumb/jsPlumb-renderers-vml-1.3.13-RC1"*/],function(){ 32 | require(["plumbing", 33 | "wsevents", 34 | "userevents"],function(){ 35 | require(["main"],function(){ 36 | }); 37 | }); 38 | }); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /secure/server-key.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEIjCCAwqgAwIBAgIJAMfikJjK+k9HMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIDAJOWTERMA8GA1UEBwwITmV3IFlvcmsxEjAQBgNVBAoM 4 | CVNwYWNlYnJldzESMBAGA1UEAwwJU3BhY2VicmV3MSMwIQYJKoZIhvcNAQkBFhRj 5 | b250YWN0QHNwYWNlYnJldy5jYzAeFw0xOTExMDYwMTI2MDBaFw0yMDExMDUwMTI2 6 | MDBaMHoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOWTERMA8GA1UEBwwITmV3IFlv 7 | cmsxEjAQBgNVBAoMCVNwYWNlYnJldzESMBAGA1UEAwwJU3BhY2VicmV3MSMwIQYJ 8 | KoZIhvcNAQkBFhRjb250YWN0QHNwYWNlYnJldy5jYzCCASIwDQYJKoZIhvcNAQEB 9 | BQADggEPADCCAQoCggEBAOyrTofM1vfnOGdwr5MSQzUFopF0zmtXSKbc42mcPgru 10 | 7z0C16ig4blNamih7JHJ1DD1yAYzB3ODyGIY8/5VCDCGo8XsnnwaBZMD2ZpWCKgh 11 | tCRBTr4SG+O2QudOltBh4SKf4Y4odfX2I1fYAKehEcZTtPfPELvDfNmzVyft5scb 12 | SiD1XEPgZiPZwea06hXKVSJQbnPAE5YB0WYCA6tUy5VUw3B4LHnJfgCiKGrXuwW1 13 | C+ohziRYC9UCkwY3W17d1aV5+VzRDtbE8t6KaXeZfqwAB37EuLOY6jYZ7zILGh16 14 | DEctDa8NVd8bSO2gkMk9ngRM/bqut+ZQhmNbwfuUwicCAwEAAaOBqjCBpzAdBgNV 15 | HQ4EFgQUEu+jViwkTsbh1/7verRTMR4druswHwYDVR0jBBgwFoAUEu+jViwkTsbh 16 | 1/7verRTMR4druswCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHwYDVR0RBBgwFoIJ 17 | bG9jYWxob3N0ggkxMjcuMC4wLjEwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2Vu 18 | ZXJhdGVkIENlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4IBAQDF+eLiUCMmxNon 19 | OoArLBkCuEeFSMyzyf/P/uv8lvLEHz6+e0oQQTTIMn0Wm5Ps2p6HjMydLdXJwy1B 20 | x7vd8vqB6nhibAUSyCwMyynRpUoXyh+kPZSUFxNGTu525U5v+V2o1euXOydOg5G5 21 | 60WNRwTabSskzCwXOCNH4cW81T02xwtY41NxsioOEbC1blD+NFdpAJ+Lc6SjpyDs 22 | HUJT7AGsKpCSOSHcnw9O8HuT72gHG/hsOJSN12fZXTP21GT7YpfaPsjnwl+VNwMT 23 | pP8MYWgIwCxgwfupzPUmxTNaIg1StjfmPpgK9dTvaoo+BqXp05XOfFj9JjnBr0IZ 24 | SqNbAjaC 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /secure/localhost.conf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | 3 | default_bits = 2048 4 | default_keyfile = server-key.pem 5 | distinguished_name = subject 6 | req_extensions = req_ext 7 | x509_extensions = x509_ext 8 | string_mask = utf8only 9 | 10 | [ subject ] 11 | 12 | countryName = Country Name (2 letter code) 13 | countryName_default = US 14 | 15 | stateOrProvinceName = State or Province Name (full name) 16 | stateOrProvinceName_default = NY 17 | 18 | localityName = Locality Name (eg, city) 19 | localityName_default = New York 20 | 21 | organizationName = Organization Name (eg, company) 22 | organizationName_default = Spacebrew 23 | 24 | commonName = Common Name (e.g. server FQDN or YOUR name) 25 | commonName_default = Spacebrew 26 | 27 | emailAddress = Email Address 28 | emailAddress_default = contact@spacebrew.cc 29 | 30 | [ x509_ext ] 31 | 32 | subjectKeyIdentifier = hash 33 | authorityKeyIdentifier = keyid,issuer 34 | 35 | basicConstraints = CA:FALSE 36 | keyUsage = digitalSignature, keyEncipherment 37 | subjectAltName = @alternate_names 38 | nsComment = "OpenSSL Generated Certificate" 39 | 40 | [ req_ext ] 41 | 42 | subjectKeyIdentifier = hash 43 | 44 | basicConstraints = CA:FALSE 45 | keyUsage = digitalSignature, keyEncipherment 46 | subjectAltName = @alternate_names 47 | nsComment = "OpenSSL Generated Certificate" 48 | 49 | [ alternate_names ] 50 | 51 | DNS.1 = localhost 52 | DNS.2 = 127.0.0.1 53 | -------------------------------------------------------------------------------- /secure/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDsq06HzNb35zhn 3 | cK+TEkM1BaKRdM5rV0im3ONpnD4K7u89AteooOG5TWpooeyRydQw9cgGMwdzg8hi 4 | GPP+VQgwhqPF7J58GgWTA9maVgioIbQkQU6+EhvjtkLnTpbQYeEin+GOKHX19iNX 5 | 2ACnoRHGU7T3zxC7w3zZs1cn7ebHG0og9VxD4GYj2cHmtOoVylUiUG5zwBOWAdFm 6 | AgOrVMuVVMNweCx5yX4Aoihq17sFtQvqIc4kWAvVApMGN1te3dWleflc0Q7WxPLe 7 | iml3mX6sAAd+xLizmOo2Ge8yCxodegxHLQ2vDVXfG0jtoJDJPZ4ETP26rrfmUIZj 8 | W8H7lMInAgMBAAECggEBAOAPCmjcj8Pg3nBnqYEmMARZkl/84pOS7dAznQTWC5LU 9 | RCBRuZQGw8xns07dxZb8Kew0NCtSUkKVR7c/gneNTxSJ0zDFZAF/rTlS0scfnlB2 10 | oBIW1hJyGH9LhSlxKKydjcP281ZfjdXPMgSvFWedwLMvekuFeIUafO+pruC5hNuH 11 | cyded+UlQeymwhj3kSmRnt4sWoRhCe6llXQBPJ+/DibG1bLjsvid1WrTyhgbaMx/ 12 | YT4uCv6wnTgzQGoREa9YMpO8ELcphf6zveOgiPpaf4qlJ5iJ3AAscW9pXrGFUOhn 13 | SDeIpR6V3dDWzL5ftUU6fmbe0sBLWmxz44+w4yQpHDkCgYEA/imlCuo34tIVhwjK 14 | pWbBHn4lePiAil9RPlCkkMQHtUdJpDfm6lEJYfA+XTaQfYrRKiKP6TjQPZCpG2eD 15 | 0wtJAoYZd+GtPUn9UzuiHYq9sxQtmpycSKkTO1Md4t2MeSaCi5QuSUIfs85Ho7yD 16 | nwSmrzqkKUa59DmriV6ka6aFD0MCgYEA7mFJ18peXAo2zpJm2W5S+QpXB8gRjurH 17 | 6TdYH6gamwDHgbGxoLXMwXt+HkMDsGYZK7le+3lcSvxJSmjc3kH9CD/stOlAIGwd 18 | cmSQuzeg8BMrzoLIYI1EKL7i/HY2a90f6Rv9tEZ2FWbuM8ITTaaisYkDBcNDIq5r 19 | 6KAJ+4J9+U0CgYEAlE32iOzsWUTe3OFrZaIUs9dFxFW0o4lE3ciuje5k1QlY1iLs 20 | cfOBNw25RijNnQPAUwbB1IObNyFPG7eGO0JNgtR+ze34aVfgIo4cZYKMeI8goSQb 21 | KWbG2wn/2V4PVq365Lk9XO/hkcifEtKwNry/CmZ8Xb1wSirQhiKcbFhJMgMCgYBT 22 | l95TQ+4PDK3nhsN2YA/MsWmPmwM9QOvVxsiqYULjNETCIFQP7XDeMLYrWojCpSsS 23 | gDDkssxAp3P0Gg3KCw32hKQ3VMxeEkvJP6bZYEqZpb9o7cfiw+cw+q+SpMm0zZhR 24 | ILW5MDRckPnbQKWQR20qIMfkj3LNiKjirKyTfFE8yQKBgADKp0+SZVUQgZw6gAMi 25 | HVQw5LRKOwPDHA3r/Sb9CkuezwaGuxD70VYZ8UodcogJL5AR+DiQbiNFBP6XdtLh 26 | FEbSj1pOOSF+JP09ahpk6tAEUCazQ756m5QDCljHd+JpxTlAUVwogkJvktfVEiYp 27 | fc4o1oogCfJMgneJatFf4jGS 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /admin/js/jsplumb/jsPlumb-drag-1.3.13-RC1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * this is experimental and probably will not be used. solutions exist for most libraries. but of course if 3 | * i want to support multiple scopes at some stage then i will have to do dragging inside jsPlumb. 4 | */ 5 | ;(function() { 6 | 7 | window.jsPlumbDrag = function(_jsPlumb) { 8 | 9 | var ta = new TouchAdapter(); 10 | 11 | this.draggable = function(selector) { 12 | var el, elId, da = [], elo, d = false, 13 | isInSelector = function(el) { 14 | if (typeof selector == "string") 15 | return selector === _jsPlumb.getId(el); 16 | 17 | for (var i = 0; i < selector.length; i++) { 18 | var _sel = jsPlumb.CurrentLibrary.getDOMElement(selector[i]); 19 | if (_sel == el) return true; 20 | } 21 | return false; 22 | }; 23 | 24 | ta.bind(document, "mousedown", function(e) { 25 | var target = e.target || e.srcElement; 26 | if (isInSelector(target)) { 27 | el = jsPlumb.CurrentLibrary.getElementObject(target); 28 | elId = _jsPlumb.getId(el); 29 | elo = jsPlumb.CurrentLibrary.getOffset(el); 30 | da = [e.pageX, e.pageY]; 31 | d = true; 32 | } 33 | }); 34 | 35 | ta.bind(document, "mousemove", function(e) { 36 | if (d) { 37 | var dx = e.pageX - da[0], 38 | dy = e.pageY - da[1]; 39 | 40 | jsPlumb.CurrentLibrary.setOffset(el, { 41 | left:elo.left + dx, 42 | top:elo.top + dy 43 | }); 44 | _jsPlumb.repaint(elId); 45 | e.preventDefault(); 46 | e.stopPropagation(); 47 | } 48 | }); 49 | ta.bind(document, "mouseup", function(e) { 50 | el = null; 51 | d = false; 52 | }); 53 | }; 54 | 55 | var isIOS = ((/iphone|ipad/gi).test(navigator.appVersion)); 56 | if (isIOS) 57 | _jsPlumb.draggable = this.draggable; 58 | 59 | }; 60 | 61 | })(); -------------------------------------------------------------------------------- /admin/js/plumbing.js: -------------------------------------------------------------------------------- 1 | var myPlumb = {}; 2 | 3 | myPlumb.endpoints = {}; 4 | myPlumb.connections = {}; 5 | myPlumb.revConnections = {}; 6 | 7 | // this is the paint style for the connecting lines.. 8 | myPlumb.connectorPaintStyle = { 9 | lineWidth:2, 10 | strokeStyle:"#000000",// "#aaaaaa", 11 | joinstyle:"round", 12 | outlineColor:"white", 13 | outlineWidth:0 14 | }; 15 | // .. and this is the hover style. 16 | myPlumb.connectorHoverStyle = { 17 | lineWidth:2, 18 | strokeStyle:"#ff0", 19 | outlineColor:"#ccc" 20 | }; 21 | myPlumb.endpointActiveStyle = { 22 | fillStyle:"magenta", 23 | outlineColor:"#000" 24 | }; 25 | myPlumb.endpointPaintStyle = { 26 | fillStyle:"#000000" 27 | }; 28 | myPlumb.endpointHoverStyle = { 29 | fillStyle:"#ff0", 30 | outlineColor:"#ccc" 31 | } 32 | myPlumb.sourceEndpoint = { 33 | isSource:true, 34 | enabled:false, 35 | anchor:"RightMiddle" 36 | }; 37 | myPlumb.targetEndpoint = { 38 | enabled:false, 39 | isTarget:true, 40 | anchor:"LeftMiddle" 41 | }; 42 | 43 | myPlumb.allSourceEndpoints = []; 44 | myPlumb.allTargetEndpoints = []; 45 | myPlumb.connectionParams; 46 | 47 | 48 | setupPlumbing = function() { 49 | //jsPlumb.setRenderMode(jsPlumb.SVG); 50 | jsPlumb.importDefaults({ 51 | // blue endpoints 7 px; green endpoints 11. 52 | Anchors:["RightMiddle","LeftMiddle"], 53 | ConnectionsDetachable:false, 54 | Connector:["Bezier",{curviness:50}], 55 | ConnectorZIndex:1, 56 | Endpoint : ["Image",{src:"img/node-open.png"}],//["Dot",{radius:7}], 57 | //Endpoints : ["Blank", "Blank"], 58 | EndpointStyle:myPlumb.endpointPaintStyle, 59 | EndpointHoverStyle:myPlumb.endpointHoverStyle, 60 | HoverPaintStyle:myPlumb.connectorHoverStyle, 61 | LogEnabled:false, 62 | MaxConnections:-1, 63 | PaintStyle:myPlumb.connectorPaintStyle, 64 | Container:endpointBin, 65 | //RenderMode:jsPlumb.SVG, 66 | setAutomaticRepaint:true 67 | }); 68 | myPlumb.connectionParams = {container:$("#connectionBin")}; 69 | triggerPaint(); 70 | }; 71 | 72 | //probably a horrible idea, but it keeps everything aligned 73 | //might want to increase the interval timeout, but then it just looks choppy 74 | triggerPaint = function(){ 75 | jsPlumb.repaintEverything(); 76 | window.requestAnimationFrame(triggerPaint); 77 | }; 78 | 79 | window.requestAnimationFrame = (window.requestAnimationFrame 80 | || window.webkitRequestAnimationFrame 81 | || window.mozRequestAnimationFrame); -------------------------------------------------------------------------------- /admin/js/utils.js: -------------------------------------------------------------------------------- 1 | //static regex and function to replace all non-alphanumeric characters 2 | //in a string with their unicode decimal surrounded by hyphens 3 | //and a regex/function pair to do the reverse 4 | String.SafetifyRegExp = new RegExp("([^a-zA-Z0-9])","gi"); 5 | String.UnsafetifyRegExp = new RegExp("-(.*?)-","gi"); 6 | String.SafetifyFunc = function(match, capture, index, full){ 7 | return "-"+capture.charCodeAt(0)+"-"; 8 | }; 9 | String.UnsafetifyFunc = function(match, capture, index, full){ 10 | return String.fromCharCode(capture); 11 | }; 12 | 13 | //create a String prototype function so we can do this directly on each string as 14 | //"my cool string".Safetify() 15 | String.prototype.Safetify = function(){ 16 | return this.replace(String.SafetifyRegExp, String.SafetifyFunc); 17 | }; 18 | String.prototype.Unsafetify = function(){ 19 | return this.replace(String.UnsafetifyRegExp, String.UnsafetifyFunc); 20 | }; 21 | 22 | //global functions so we can call ['hello','there'].map(Safetify) 23 | Safetify = function(s){ 24 | return s.Safetify(); 25 | }; 26 | Unsafetify = function(s){ 27 | return s.Unsafetify(); 28 | }; 29 | 30 | //handlebar helper so we can use Safetify in our handlebar templates 31 | //{{Safetify some.cool.property}} 32 | Handlebars.registerHelper('Safetify', function(val){ 33 | return val.Safetify(); 34 | }); 35 | 36 | //handlebar helper to provide an {{index}} property in our each loops 37 | //{{#each_with_index people}} 38 | //
#{{index}} - hello {{name}}
39 | //{{/each_with_index}} 40 | Handlebars.registerHelper("each_with_index", function(array, fn) { 41 | var buffer = ""; 42 | for (var i = 0, j = array.length; i < j; i++) { 43 | var item = array[i]; 44 | 45 | // stick an index property onto the item, starting with 0, may make configurable later 46 | item.index = i; 47 | //also adding an even/odd text for classing 48 | item.even = ((i%2)===0?"even":"odd"); 49 | 50 | // show the inside of the block 51 | buffer += fn(item); 52 | } 53 | 54 | // return the finished buffer 55 | return buffer; 56 | 57 | }); 58 | 59 | //get the value of the requested key in the querystring 60 | //if the key does not exist in the query string, returns the empty string 61 | function gup( name ) { 62 | name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); 63 | var regexS = "[\\?&]"+name+"=([^&#]*)"; 64 | var regex = new RegExp( regexS ); 65 | var results = regex.exec( window.location.href ); 66 | if( results == null ) 67 | return ""; 68 | else 69 | return results[1]; 70 | }; 71 | 72 | /** 73 | * Load handlebars templates from external files 74 | */ 75 | getTemplateAjax = function(path, callback) { 76 | $.ajax({ 77 | url: path, 78 | dataType: "html", 79 | cache: false, 80 | success: callback 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /admin/js/jsplumb/jsPlumb-overlays-guidelines-1.3.13-RC1.js: -------------------------------------------------------------------------------- 1 | // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. 2 | jsPlumb.Overlays.GuideLines = function() { 3 | var self = this; 4 | self.length = 50; 5 | self.lineWidth = 5; 6 | this.type = "GuideLines"; 7 | AbstractOverlay.apply(this, arguments); 8 | jsPlumb.jsPlumbUIComponent.apply(this, arguments); 9 | this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { 10 | 11 | var head = connector.pointAlongPathFrom(self.loc, self.length / 2), 12 | mid = connector.pointOnPath(self.loc), 13 | tail = jsPlumbUtil.pointOnLine(head, mid, self.length), 14 | tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), 15 | headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); 16 | 17 | self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); 18 | 19 | return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; 20 | }; 21 | 22 | this.computeMaxSize = function() { return 50; }; 23 | 24 | this.cleanup = function() { }; // nothing to clean up for GuideLines 25 | }; 26 | 27 | // a test 28 | jsPlumb.Overlays.svg.GuideLines = function() { 29 | var path = null, self = this, path2 = null, p1_1, p1_2; 30 | jsPlumb.Overlays.GuideLines.apply(this, arguments); 31 | this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { 32 | if (path == null) { 33 | path = _node("path"); 34 | connector.svg.appendChild(path); 35 | self.attachListeners(path, connector); 36 | self.attachListeners(path, self); 37 | 38 | p1_1 = _node("path"); 39 | connector.svg.appendChild(p1_1); 40 | self.attachListeners(p1_1, connector); 41 | self.attachListeners(p1_1, self); 42 | 43 | p1_2 = _node("path"); 44 | connector.svg.appendChild(p1_2); 45 | self.attachListeners(p1_2, connector); 46 | self.attachListeners(p1_2, self); 47 | 48 | } 49 | 50 | _attr(path, { 51 | "d" : makePath(d[0], d[1]), 52 | stroke : "red", 53 | fill : null 54 | }); 55 | 56 | _attr(p1_1, { 57 | "d" : makePath(d[2][0], d[2][1]), 58 | stroke : "blue", 59 | fill : null 60 | }); 61 | 62 | _attr(p1_2, { 63 | "d" : makePath(d[3][0], d[3][1]), 64 | stroke : "green", 65 | fill : null 66 | }); 67 | }; 68 | 69 | var makePath = function(d1, d2) { 70 | return "M " + d1.x + "," + d1.y + 71 | " L" + d2.x + "," + d2.y; 72 | }; 73 | }; -------------------------------------------------------------------------------- /schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "id":"http://spacebrew.cc/messaging-schema#", 3 | "$schema":"http://json-schema.org/draft-04/schema#", 4 | "description":"a schema defining the messages sent by clients and admins", 5 | "type":"object", 6 | "oneOf":[ 7 | {"$ref":"#/definitions/config"}, 8 | {"$ref":"#/definitions/message"}, 9 | {"$ref":"#/definitions/admin"}, 10 | {"$ref":"#/definitions/route"}], 11 | "definitions":{ 12 | "config":{ 13 | "type":"object", 14 | "required":["config"], 15 | "properties":{ 16 | "config":{ 17 | "type":"object", 18 | "required":["name","description"], 19 | "properties":{ 20 | "name":{"type":"string"}, 21 | "description":{"type":"string"}, 22 | "subscribe":{ 23 | "type":"object", 24 | "properties":{ 25 | "messages":{ 26 | "type":"array", 27 | "items":{ 28 | "type":"object", 29 | "required":["type","name"], 30 | "properties":{ 31 | "type":{"type":"string"}, 32 | "name":{"type":"string"}, 33 | "default":{}}}}}}, 34 | "publish":{ 35 | "type":"object", 36 | "properties":{ 37 | "messages":{ 38 | "type":"array", 39 | "items":{ 40 | "type":"object", 41 | "required":["type","name"], 42 | "properties":{ 43 | "type":{"type":"string"}, 44 | "name":{"type":"string"}, 45 | "default":{}}}}}}, 46 | "options":{"type":"object"}}}}}, 47 | "message":{ 48 | "type":"object", 49 | "required":["message"], 50 | "properties":{ 51 | "message":{ 52 | "type":"object", 53 | "required":["clientName","name","type","value"], 54 | "properties":{ 55 | "clientName":{"type":"string"}, 56 | "name":{"type":"string"}, 57 | "type":{"type":"string"}, 58 | "value":{}}}}}, 59 | "admin":{ 60 | "type":"object", 61 | "required":["admin"], 62 | "properties":{ 63 | "admin":{}, 64 | "no_msgs":{}}}, 65 | "route":{ 66 | "type":"object", 67 | "required":["route"], 68 | "properties":{ 69 | "route":{ 70 | "type":"object", 71 | "required":["publisher","subscriber","type"], 72 | "properties":{ 73 | "type":{"enum":["add","remove"]}, 74 | "publisher":{ 75 | "type":"object", 76 | "required":["type","clientName","remoteAddress","name"], 77 | "properties":{ 78 | "type":{"type":"string"}, 79 | "clientName":{"type":"string"}, 80 | "name":{"type":"string"}, 81 | "remoteAddress":{"type":"string"}}, 82 | "subscriber":{ 83 | "type":"object", 84 | "required":["type","clientName","remoteAddress","name"], 85 | "properties":{ 86 | "type":{"type":"string"}, 87 | "clientName":{"type":"string"}, 88 | "name":{"type":"string"}, 89 | "remoteAddress":{"type":"string"}}}}}}}} 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /admin/js/main.js: -------------------------------------------------------------------------------- 1 | $(window).bind('load', function(){ 2 | $(document).ready(function(){ 3 | jsPlumb.ready(function(){ 4 | setupPlumbing(); 5 | setupWebsocket(); 6 | setupUserEvents(); 7 | setupLinks(); 8 | }); 9 | }); 10 | }); 11 | 12 | var NONE_SELECTED = {}, 13 | PUB_SELECTED = {}, 14 | SUB_SELECTED = {}; 15 | var currState = NONE_SELECTED; 16 | 17 | var setupLinks = function() { 18 | $(".about-link").on("click", function(event) { 19 | window.open("http://docs.spacebrew.cc"); 20 | return false; 21 | }) 22 | $(".contact-link").on("click", function(event) { 23 | window.open("http://docs.spacebrew.cc/contact/"); 24 | return false; 25 | }) 26 | $(".lab-link").on("click", function(event) { 27 | window.open("http://www.rockwellgroup.com/lab"); 28 | return false; 29 | }) 30 | } 31 | 32 | var dorouteradio = function(e){ 33 | if (e){e.preventDefault();}; 34 | var selectedPub = $("input[name=pub]:radio:checked").val(); 35 | var selectedSub = $("input[name=sub]:radio:checked").val(); 36 | if (selectedPub && selectedSub){ 37 | selectedPub = selectedPub.split('_').map(Unsafetify); 38 | selectedSub = selectedSub.split('_').map(Unsafetify); 39 | if (selectedPub.length == 4 && selectedSub.length == 4){ 40 | ws.send(JSON.stringify({ 41 | route:{type:'add', 42 | publisher:{clientName:selectedPub[0], 43 | name:selectedPub[2], 44 | type:selectedPub[3], 45 | remoteAddress:selectedPub[1]}, 46 | subscriber:{clientName:selectedSub[0], 47 | name:selectedSub[2], 48 | type:selectedSub[3], 49 | remoteAddress:selectedSub[1]}} 50 | })); 51 | } 52 | } 53 | }; 54 | 55 | var changeRoute = function(changeType, fromId, toId){ 56 | var selectedPub = fromId.split('_').map(Unsafetify); 57 | var selectedSub = toId.split('_').map(Unsafetify); 58 | var clientName = 1, 59 | remoteAddress = clientName + 1, 60 | name = remoteAddress + 1, 61 | type = name + 1; 62 | if (selectedPub.length == type + 1 && selectedSub.length == type + 1){ 63 | var m = { 64 | route:{type:changeType, 65 | publisher:{clientName:selectedPub[clientName], 66 | name:selectedPub[name], 67 | type:selectedPub[type], 68 | remoteAddress:selectedPub[remoteAddress]}, 69 | subscriber:{clientName:selectedSub[clientName], 70 | name:selectedSub[name], 71 | type:selectedSub[type], 72 | remoteAddress:selectedSub[remoteAddress]}} 73 | }; 74 | if (debug) console.log(m); 75 | ws.send(JSON.stringify(m)); 76 | } 77 | } 78 | 79 | var addRoute = function(fromId, toId){ 80 | changeRoute("add", fromId, toId); 81 | } 82 | 83 | var removeRoute = function(fromId, toId){ 84 | changeRoute("remove", fromId, toId); 85 | } 86 | 87 | var dorouteremove = function(index){ 88 | if (index >= 0 && index < routes.length){ 89 | var toRemove = routes.splice(index, 1); 90 | if (toRemove.length > 0){ 91 | toRemove = toRemove[0]; 92 | ws.send(JSON.stringify({ 93 | route:{type:'remove', 94 | publisher:toRemove.publisher, 95 | subscriber:toRemove.subscriber} 96 | })); 97 | } 98 | } 99 | }; -------------------------------------------------------------------------------- /node_server_forever.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Spacebrew Server with Forever 5 | * ------------------------------ 6 | * 7 | * This script runs the Spacebrew server, and optionally the spacebrew live 8 | * persistent router, in forever mode. This means that the server is automatically 9 | * relaunched if it crashes. All standard node_server.js options are supported. 10 | * To find out about the comand line flags just run the script with the '-h' or 11 | * '--help' flag. 12 | * 13 | * Latest Updates: 14 | * - checks if data/log folder already exists, and if not, it creates folder 15 | * 16 | * @author: Quin Kennedy, Julio Terra, and other contributors 17 | * @filename: node_server.js 18 | * @date: June 1st, 2013 19 | * @updated with version: 0.3.1 20 | * 21 | */ 22 | 23 | //DANGEROUS DEBUG ONLY 24 | //from https://stackoverflow.com/questions/20433287/node-js-request-cert-has-expired 25 | // process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 26 | 27 | var forever = require('forever-monitor') 28 | , fs = require('fs') 29 | , logger = require('./logger') 30 | , argv = process.argv.splice(2, process.argv.length) 31 | , restarts = 0 32 | , date = Date.parse(new Date) 33 | , help = false 34 | , data_dir = __dirname + "/data" 35 | , log_dir = __dirname + "/data/log" 36 | ; 37 | 38 | 39 | /** 40 | * check if data/log directory already exists, and if not, then create it. 41 | */ 42 | var setupLogDirectory = function() { 43 | // check if data folder exists 44 | try { 45 | fs.statSync(data_dir); 46 | } 47 | catch (e) { 48 | fs.mkdir(data_dir, err => { if (err) console.log(err) }); 49 | logger.log("info", "creating data directory"); 50 | } 51 | 52 | // check if data/log folder exists 53 | try { 54 | fs.statSync(log_dir); 55 | } 56 | catch (e) { 57 | fs.mkdir(__dirname + "/data/log", err => { if (err) console.log(err) }); 58 | logger.log("info", "creating data/log directory"); 59 | } 60 | } 61 | 62 | /** 63 | * Parses CLI arguments to confirm if there are any commands that need to occur before 64 | * the app is launched in forever mode 65 | */ 66 | var processArguments = function(){ 67 | for(var i = 0; i < argv.length; i++){ 68 | switch(argv[i]){ 69 | case "-l": 70 | case "--log": 71 | logger.debugLevel = "info"; 72 | break; 73 | case "--secure": 74 | logger.log('warn', 'running secure'); 75 | case "--loglevel": 76 | logger.debugLevel = argv[(i += 1)]; 77 | break; case "-x": 78 | case "--cleanstart": 79 | try { 80 | fs.unlinkSync('./data/routes/live/live_persist_config.json'); 81 | } 82 | catch (e) { 83 | logger.log('warn', "[processArguments] not able to delete /data/routes/live/live_persist_config.json") 84 | } 85 | break; 86 | case "-h": 87 | case "--help": 88 | help = true; 89 | break; 90 | } 91 | } 92 | } 93 | 94 | 95 | var createForeverServer = function() { 96 | 97 | /** 98 | * Forever server configurations for launching the spacebrew server in forever mode 99 | * @type {forever.Monitor} 100 | */ 101 | var server = new (forever.Monitor)('node_server.js', { 102 | 'silent': false 103 | , 'options': argv 104 | , 'uid': 'spacebrew' 105 | , 'pid': './data/' 106 | , 'logFile': './data/log/spacebrew_forever_' + date + '.log' 107 | , 'outFile': './data/log/spacebrew_info_' + date + '.log' 108 | , 'errFile': './data/log/spacebrew_error_' + date + '.log' 109 | }); 110 | 111 | /** 112 | * Register event handler for application exit events 113 | * @return {[type]} [description] 114 | */ 115 | server.on('exit', function () { 116 | logger.log('info','[Exit] the spacebrew server will no longer be restarted'); 117 | }); 118 | 119 | /** 120 | * Register event handler for spacebrew server restart events, due to app crashing 121 | */ 122 | server.on('restart', function () { 123 | restarts += 1; 124 | date = Date.parse(new Date); 125 | 126 | // if script was run with help flag then stop after first restart 127 | if (help) { 128 | process.exit(); 129 | } 130 | 131 | // otherwise, print restart count message 132 | else { 133 | logger.log('warn','[Restart] the spacebrew server has been restarted ' + restarts + ' time'); 134 | } 135 | }); 136 | 137 | server.start(); 138 | } 139 | 140 | setupLogDirectory(); 141 | processArguments(); 142 | createForeverServer(); 143 | 144 | -------------------------------------------------------------------------------- /node_persistent_live.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Spacebrew Live Persist Server 3 | * ----------------------------- 4 | * 5 | * This script runs the Spacebrew Live Persist module as a standalone server. 6 | * It can be used to add live persist functionality to any spacebrew server, 7 | * local or remote. 8 | * 9 | * @author: Julio Terra 10 | * @filename: node_persistent_live.js 11 | * @date: May 31, 2013 12 | * @updated with version: 0.3.0 13 | * 14 | */ 15 | 16 | 17 | var logger = require('./logger') 18 | , livePersister = require('./spacebrew_live_persist') 19 | , port = 9000 20 | , host = "localhost" 21 | , help = false 22 | ; 23 | 24 | /** 25 | * Prints startup message to console/terminal 26 | */ 27 | var printStartupMsg = function() { 28 | console.log(""); 29 | console.log("This is a tool for persisting all routes created in the standard spacebrew admin."); 30 | console.log("Connecting to spacebrew server at " + host + ":" + port + "."); 31 | console.log(""); 32 | console.log("==========================================="); 33 | console.log(""); 34 | } 35 | 36 | /** 37 | * Processes the command line arguments when app is launched 38 | */ 39 | var processArguments = function(){ 40 | var argv = process.argv; 41 | for(var i = 2; i < argv.length; i++){ 42 | switch(argv[i]){ 43 | case "-console.log": 44 | case "--log": 45 | setLogLevel("info"); 46 | break; 47 | case "--loglevel": 48 | setLogLevel( argv[i++] ); 49 | break; 50 | case "--host": 51 | setSpacebrewHost(argv[++i]); 52 | break; 53 | case "-p": 54 | case "--port": 55 | setSpacebrewPort(argv[++i]); 56 | break; 57 | case "-h": 58 | case "--help": 59 | printHelp(); 60 | help = true; 61 | break; 62 | } 63 | } 64 | }; 65 | 66 | /** 67 | * Set the port of the spacebrew server. defaults to 9000. Can be overridden using the 68 | * flag -p or --port when starting up the persistent router. 69 | * 70 | * @type {Number} 71 | */ 72 | var setSpacebrewPort = function( newPort ){ 73 | newPort = parseInt( newPort ); 74 | //check that tempPort is a number and within valid port range 75 | if (!isNaN( newPort ) && newPort >= 1 && newPort <= 65535){ 76 | port = tempPort; 77 | logger.log("info", "[setSpacebrewHost] set spacebrew port to " + newPort); 78 | } 79 | }; 80 | 81 | /** 82 | * Set the hostname of the device that hosts the spacebrew server. defaults to localhost. Can 83 | * be overridden using the flag -h or --host when starting up the persistent router. 84 | * 85 | * @type {String} 86 | */ 87 | var setSpacebrewHost = function ( newHost ){ 88 | host = newHost; 89 | logger.log("info", "[setSpacebrewHost] set spacebrew host to " + newHost); 90 | } 91 | 92 | /** 93 | * method that is used to set the log level when user sets log level via command line 94 | * 95 | * @param {String} newLevel New log level - "error", "warn", "debug", or "info" 96 | */ 97 | var setLogLevel = function( newLevel ) { 98 | logger.debugLevel = newLevel; 99 | logger.log("info", "[setLogLevel] log level set to " + newLevel); 100 | } 101 | 102 | /** 103 | * Prints the node server help message to screen 104 | */ 105 | var printHelp = function (){ 106 | console.log("command line parameters:"); 107 | console.log("\t--port (-p): set the port of the spacebrew server (default 9000)"); 108 | console.log("\t--host: the hostname of the spacebrew server (default localhost)"); 109 | console.log("\t--help (-h): print this help text"); 110 | console.log("examples:"); 111 | console.log("\tnode spacebrew_live_persist.js -p 9011"); 112 | console.log("\tnode spacebrew_live_persist.js -h my-sweet-computer"); 113 | console.log(""); 114 | }; 115 | 116 | /** 117 | * Method that handles key app functions including printing app startup message, processing 118 | * command line arguments, and starting up persistent server 119 | */ 120 | var main = function() { 121 | var persist_configs = {}; 122 | 123 | printStartupMsg(); 124 | processArguments(); 125 | 126 | // if app command included help flag then don't run app 127 | if (help) { 128 | process.exit(); 129 | } 130 | 131 | // if app command did not include help flag then start-up persist server 132 | else { 133 | persist_configs = { 134 | "host": host, 135 | "port": port, 136 | "logLevel": logger.debugLevel 137 | } 138 | livePersister.persistRoutes( persist_configs ); 139 | } 140 | } 141 | 142 | /** 143 | * Run the app 144 | */ 145 | main(); -------------------------------------------------------------------------------- /admin/js/userevents.js: -------------------------------------------------------------------------------- 1 | var setupUserEvents = function(){ 2 | //clicking & hovering over publishers and subscribers is handled when 3 | //the publishers and subscribers are created in wsevents.js 4 | 5 | //setup click handler when nothing else is clicked 6 | $(document.body).click(clickBody); 7 | }; 8 | 9 | var clickInfo = function(event){ 10 | if (debug) console.log("clicked!"); 11 | $("#"+event.target.id.substr("button_".length)).toggleClass('show'); 12 | }; 13 | 14 | // var clickDelete = function(event){ 15 | // if (debug) console.log("clicked!"); 16 | // }; 17 | 18 | var clickBody = function(event){ 19 | event.preventDefault(); 20 | //if we are in a selected state, then when we click somewhere innocuous, 21 | //we should go to an unselected state 22 | if (currState != NONE_SELECTED){ 23 | if (!event.target.classList.contains("itemwrapper")){ 24 | turnOffSelected(); 25 | } 26 | // //if the target matches any specific whitelist elements 27 | // //then turn off selected mode 28 | // var id = event.target.id; 29 | // var okIds = ['client_list','scroll_padding']; 30 | // var numIds = okIds.length; 31 | // while(numIds--){ 32 | // if (id == okIds[numIds]){ 33 | // turnOffSelected(); 34 | // return; 35 | // } 36 | // } 37 | // //otherwise, if the target matches any whitelist element classes 38 | // //then turn off selected mode 39 | // var classList = event.target.classList; 40 | // var okClasses = ['clientrow','header','headerrow']; 41 | // var numClasses = okClasses.length; 42 | // while (numClasses--){ 43 | // if (classList.contains(okClasses)){ 44 | // turnOffSelected(); 45 | // return; 46 | // } 47 | // } 48 | } 49 | }; 50 | 51 | var overItem = function(event){ 52 | event.preventDefault(); 53 | if (currState == NONE_SELECTED){ 54 | var item = $(event.target).children(".item"); 55 | var pub = item.hasClass("publisher"); 56 | var type = getItemType(item); 57 | var itemId = item.prop('id'); 58 | var pieces = itemId.split('_'); 59 | var context = { 60 | type:type, 61 | id:item.prop('id'), 62 | pub:pub, 63 | clientid:pieces[1]+'_'+pieces[2] 64 | }; 65 | if (debug) console.log(pieces[1]+'_'+pieces[2]); 66 | $("style#selected").text(cssSelectedTemplate(context)); 67 | } 68 | }; 69 | 70 | var outItem = function(event){ 71 | event.preventDefault(); 72 | if (currState == NONE_SELECTED){ 73 | $("style#selected").text(''); 74 | } 75 | }; 76 | 77 | var clickItem = function(event){ 78 | event.preventDefault(); 79 | var item; 80 | if (event.target.classList.contains("deletebutton")){ 81 | item = $(event.target).parent(); 82 | } else { 83 | item = $(event.target).children(".item"); 84 | } 85 | var pub = item.hasClass("publisher"); 86 | var type = getItemType(item); 87 | if (currState == NONE_SELECTED){ 88 | firstClick(item, type, pub); 89 | } else { 90 | secondClick(item, type, pub); 91 | } 92 | }; 93 | 94 | var firstClick = function(item, type, pub){ 95 | item.addClass("selected"); 96 | item.closest(".clientrow").addClass("selected"); 97 | var context = { 98 | clicked:true, 99 | type:type, 100 | id:item.prop('id'), 101 | pub:pub 102 | }; 103 | var cssFile = cssTypeTemplate(context); 104 | if (debug) console.log('turning on ' + cssFile); 105 | $("style#selected").text(cssSelectedTemplate(context));// attr('href',cssFile); 106 | if (pub){ 107 | currState = PUB_SELECTED; 108 | } else { 109 | currState = SUB_SELECTED; 110 | } 111 | }; 112 | 113 | var turnOffSelected = function(){ 114 | if (debug) console.log('turning off'); 115 | $("style#selected").text(''); 116 | $(".item.selected").removeClass('selected'); 117 | $(".clientrow.selected").removeClass('selected'); 118 | currState = NONE_SELECTED; 119 | } 120 | 121 | var secondClick = function(item, type, pub){ 122 | var pubSelected = (currState == PUB_SELECTED); 123 | //if we clicked in the same column as the first click, 124 | //then turn off 'selected' mode 125 | if ((pubSelected && pub) || 126 | (!pubSelected && !pub)){ 127 | turnOffSelected(); 128 | } else { 129 | var activeItem = $(pubSelected ? ".publisher.selected" : ".subscriber.selected"); 130 | var activeType = getItemType(activeItem); 131 | //only do something if we clicked on a similar-type item 132 | if (type == activeType){ 133 | //trigger (un)routing 134 | var activeId = activeItem.prop('id'); 135 | var isSelected = item.hasClass(activeId); 136 | var myId = item.prop('id'); 137 | var pubId = pubSelected ? activeId : myId; 138 | var subId = pubSelected ? myId : activeId; 139 | //if this is selected, then we need to unroute it 140 | if (isSelected){ 141 | removeRoute(pubId, subId); 142 | } else { 143 | //otherwise, route it 144 | addRoute(pubId, subId); 145 | } 146 | } 147 | } 148 | turnOffSelected(); 149 | }; 150 | 151 | var getItemType = function(_item){ 152 | return _item.hasClass("boolean") ? "boolean" : _item.hasClass("string") ? "string" : _item.hasClass("number") ? "number" : "range"; 153 | }; 154 | 155 | var cssTypeTemplate = Handlebars.compile("css/{{pub}}_{{type}}.css"); 156 | var cssSelectedTemplate = Handlebars.compile(document.getElementById( 'active_css_handlebar' ).textContent); 157 | -------------------------------------------------------------------------------- /admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Spacebrew Admin HTTPS 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 |
22 |
23 |
24 | /Clients 25 |
26 |
27 | /Publishers 28 |
29 |
30 |
31 | /Subscribers 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 | 46 | 48 | 49 | 50 | 51 | 68 | 92 | 102 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /admin/js/jsplumb/jsPlumb-dom-adapter-1.3.13-RC1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jsPlumb 3 | * 4 | * Title:jsPlumb 1.3.13 5 | * 6 | * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas 7 | * elements, or VML. 8 | * 9 | * This file contains the base functionality for DOM type adapters. 10 | * 11 | * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) 12 | * 13 | * http://jsplumb.org 14 | * http://github.com/sporritt/jsplumb 15 | * http://code.google.com/p/jsplumb 16 | * 17 | * Dual licensed under the MIT and GPL2 licenses. 18 | */ 19 | ;(function() { 20 | 21 | var canvasAvailable = !!document.createElement('canvas').getContext, 22 | svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), 23 | // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser 24 | vmlAvailable = function() { 25 | if(vmlAvailable.vml == undefined) { 26 | var a = document.body.appendChild(document.createElement('div')); 27 | a.innerHTML = ''; 28 | var b = a.firstChild; 29 | b.style.behavior = "url(#default#VML)"; 30 | vmlAvailable.vml = b ? typeof b.adj == "object": true; 31 | a.parentNode.removeChild(a); 32 | } 33 | return vmlAvailable.vml; 34 | }; 35 | 36 | /** 37 | Manages dragging for some instance of jsPlumb. 38 | 39 | TODO move to DOM adapter 40 | 41 | */ 42 | var DragManager = function(_currentInstance) { 43 | 44 | var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; 45 | 46 | /** 47 | register some element as draggable. right now the drag init stuff is done elsewhere, and it is 48 | possible that will continue to be the case. 49 | */ 50 | this.register = function(el) { 51 | var jpcl = jsPlumb.CurrentLibrary; 52 | el = jpcl.getElementObject(el); 53 | var id = _currentInstance.getId(el), 54 | domEl = jpcl.getDOMElement(el); 55 | if (!_draggables[id]) { 56 | _draggables[id] = el; 57 | _dlist.push(el); 58 | _delements[id] = {}; 59 | } 60 | 61 | // look for child elements that have endpoints and register them against this draggable. 62 | var _oneLevel = function(p) { 63 | if (p) { 64 | var pEl = jpcl.getElementObject(p), 65 | pOff = jpcl.getOffset(pEl); 66 | 67 | for (var i = 0; i < p.childNodes.length; i++) { 68 | if (p.childNodes[i].nodeType != 3) { 69 | var cEl = jpcl.getElementObject(p.childNodes[i]), 70 | cid = _currentInstance.getId(cEl, null, true); 71 | if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { 72 | var cOff = jpcl.getOffset(cEl); 73 | _delements[id][cid] = { 74 | id:cid, 75 | offset:{ 76 | left:cOff.left - pOff.left, 77 | top:cOff.top - pOff.top 78 | } 79 | }; 80 | } 81 | } 82 | } 83 | } 84 | }; 85 | 86 | _oneLevel(domEl); 87 | }; 88 | 89 | /** 90 | notification that an endpoint was added to the given el. we go up from that el's parent 91 | node, looking for a parent that has been registered as a draggable. if we find one, we add this 92 | el to that parent's list of elements to update on drag (if it is not there already) 93 | */ 94 | this.endpointAdded = function(el) { 95 | var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), 96 | p = c.parentNode, done = p == b; 97 | 98 | _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; 99 | 100 | while (p != b) { 101 | var pid = _currentInstance.getId(p); 102 | if (_draggables[pid]) { 103 | var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); 104 | 105 | if (_delements[pid][id] == null) { 106 | var cLoc = jsPlumb.CurrentLibrary.getOffset(el); 107 | _delements[pid][id] = { 108 | id:id, 109 | offset:{ 110 | left:cLoc.left - pLoc.left, 111 | top:cLoc.top - pLoc.top 112 | } 113 | }; 114 | } 115 | break; 116 | } 117 | p = p.parentNode; 118 | } 119 | }; 120 | 121 | this.endpointDeleted = function(endpoint) { 122 | if (_elementsWithEndpoints[endpoint.elementId]) { 123 | _elementsWithEndpoints[endpoint.elementId]--; 124 | if (_elementsWithEndpoints[endpoint.elementId] <= 0) { 125 | for (var i in _delements) { 126 | delete _delements[i][endpoint.elementId]; 127 | } 128 | } 129 | } 130 | }; 131 | 132 | this.getElementsForDraggable = function(id) { 133 | return _delements[id]; 134 | }; 135 | 136 | this.reset = function() { 137 | _draggables = {}; 138 | _dlist = []; 139 | _delements = {}; 140 | _elementsWithEndpoints = {}; 141 | }; 142 | 143 | }; 144 | 145 | // for those browsers that dont have it. they still don't have it! but at least they won't crash. 146 | if (!window.console) 147 | window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; 148 | 149 | window.jsPlumbAdapter = { 150 | 151 | headless:false, 152 | 153 | appendToRoot : function(node) { 154 | document.body.appendChild(node); 155 | }, 156 | getRenderModes : function() { 157 | return [ "canvas", "svg", "vml" ] 158 | }, 159 | isRenderModeAvailable : function(m) { 160 | return { 161 | "canvas":canvasAvailable, 162 | "svg":svgAvailable, 163 | "vml":vmlAvailable() 164 | }[m]; 165 | }, 166 | getDragManager : function(_jsPlumb) { 167 | return new DragManager(_jsPlumb); 168 | }, 169 | setRenderMode : function(mode) { 170 | var renderMode; 171 | 172 | if (mode) { 173 | mode = mode.toLowerCase(); 174 | 175 | var canvasAvailable = this.isRenderModeAvailable("canvas"), 176 | svgAvailable = this.isRenderModeAvailable("svg"), 177 | vmlAvailable = this.isRenderModeAvailable("vml"); 178 | 179 | //if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); 180 | // now test we actually have the capability to do this. 181 | if (mode === "svg") { 182 | if (svgAvailable) renderMode = "svg" 183 | else if (canvasAvailable) renderMode = "canvas" 184 | else if (vmlAvailable) renderMode = "vml" 185 | } 186 | else if (mode === "canvas" && canvasAvailable) renderMode = "canvas"; 187 | else if (vmlAvailable) renderMode = "vml"; 188 | } 189 | 190 | return renderMode; 191 | } 192 | }; 193 | 194 | })(); -------------------------------------------------------------------------------- /node_server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Spacebrew Server 5 | * ---------------- 6 | * 7 | * This script runs the Spacebrew server, and optionally the spacebrew live 8 | * persistent router. In order to find out about the comand line flags just 9 | * run the script with '-h' or '--help' flag. 10 | * 11 | * @author: Quin Kennedy, Julio Terra, and other contributors 12 | * @filename: node_server.js 13 | * @date: May 31, 2013 14 | * @updated with version: 0.3.0 15 | * 16 | */ 17 | 18 | var spacebrew = require('./spacebrew') 19 | , persister = require('./spacebrew_live_persist') 20 | , logger = require('./logger') 21 | ; 22 | 23 | var defaultPort = 9000 24 | , secure = false 25 | , forceClose = false 26 | , doPing = true 27 | , persist = true 28 | , help = false 29 | ; 30 | 31 | /** 32 | * Prints startup message to console/terminal 33 | */ 34 | var printStartupMsg = function() { 35 | console.log(""); 36 | console.log("Running Spacebrew, start with argument '--help' to see available configuration arguments."); 37 | console.log("More info at http://www.spacebrew.cc"); 38 | console.log(""); 39 | } 40 | 41 | /** 42 | * Processes the command line arguments when app is launched 43 | */ 44 | var processArguments = function(){ 45 | var argv = process.argv; 46 | for(var i = 0; i < argv.length; i++){ 47 | switch(argv[i]){ 48 | case "-l": 49 | case "--log": 50 | setLogLevel("info"); 51 | break; 52 | case "--loglevel": 53 | setLogLevel( argv[(i += 1)] ); 54 | break; 55 | case "-h": 56 | case "--help": 57 | printHelp(); 58 | help = true; 59 | break; 60 | case "-p": 61 | case "--port": 62 | setDefaultPort(argv[++i]); 63 | break; 64 | case "--secure": 65 | secure = true; 66 | break; 67 | case "-c": 68 | case "--close": 69 | forceClose = true; 70 | break; 71 | case "-t": 72 | case "--timeout": 73 | forceClose = true; 74 | setCloseTimeout(argv[++i]); 75 | break; 76 | case "--ping": 77 | doPing = true; 78 | break; 79 | case "--noping": 80 | doPing = false; 81 | break; 82 | case "--pinginterval": 83 | doPing = true; 84 | setPingIntervalTime(argv[++i]); 85 | break; 86 | case "--persist": 87 | persist = true; 88 | break; 89 | case "--nopersist": 90 | persist = false; 91 | break; 92 | } 93 | } 94 | }; 95 | 96 | /** 97 | * Set the port to open for ws connections. defaults to 9000. 98 | * 99 | * @type {Number} 100 | */ 101 | var setDefaultPort = function(newPort){ 102 | var tempPort = parseInt(newPort, 10); 103 | //check that tempPort != NaN 104 | //and that the port is in the valid port range 105 | if (tempPort == tempPort && 106 | tempPort >= 1 && tempPort <= 65535){ 107 | defaultPort = tempPort; 108 | } 109 | logger.log("info", "[setDefaultPort] port set to " + defaultPort); 110 | 111 | }; 112 | 113 | var closeTimeout = 10000;//default to 10 seconds 114 | var setCloseTimeout = function(newTimeout){ 115 | var tempTimeout = parseInt(newTimeout); 116 | if (tempTimeout == tempTimeout && tempTimeout > 0){ 117 | closeTimeout = tempTimeout; 118 | } 119 | }; 120 | 121 | var pingIntervalTime = 1000;//every second 122 | var setPingIntervalTime = function( newInterval ){ 123 | var tempInterval = parseInt(newInterval); 124 | if (tempInterval == tempInterval && tempInterval > 0){ 125 | pingIntervalTime = tempInterval; 126 | } 127 | }; 128 | 129 | /** 130 | * method that is used to set the log level when user sets log level via command line 131 | * 132 | * @param {String} newLevel New log level - "error", "warn", "debug", or "info" 133 | */ 134 | var setLogLevel = function( newLevel ) { 135 | logger.debugLevel = newLevel; 136 | logger.log("info", "[setLogLevel] log level set to " + logger.debugLevel); 137 | } 138 | 139 | /** 140 | * Prints the node server help message to screen 141 | */ 142 | var printHelp = function(){ 143 | console.log(""); 144 | console.log("command line parameters:"); 145 | console.log("\t--port (-p): set the port of the spacebrew server (default 9000)"); 146 | console.log("\t--secure: [ALPHA] run the spacebrew server on https with secure sockets (default false)"); 147 | console.log("\t--help (-h): print this help text"); 148 | console.log("\t--close (-c): force close clients that don't respond to pings"); 149 | console.log("\t--timeout (-t): minimum number of ms to wait for response pong before force closing (implies --close, default 10000 [10 seconds])"); 150 | console.log("\t--ping: enable pinging of clients to track who is potentially disconnected (default)"); 151 | console.log("\t--noping: opposite of --ping"); 152 | console.log("\t--persist: enables the live route persister, which saves route configurations"); 153 | console.log("\t--nopersist: opposite of --persist"); 154 | console.log("\t--pinginterval: the number of ms between pings (implies --ping, default 1000 [1 second])"); 155 | console.log("\t--log (-l): sets logging to debug level"); 156 | console.log("\t--loglevel: set logging to info, debug, warn, error or critical - not fully supported yet"); 157 | console.log("examples:"); 158 | console.log("\tnode node_server.js -p 9011 -t 1000 --pinginterval 1000"); 159 | console.log("\tnode node_server.js --noping"); 160 | console.log(""); 161 | }; 162 | 163 | /** 164 | * Method that handles key app functions including printing app startup message, processing 165 | * command line arguments, and starting up the spacebrew and persistent servers 166 | */ 167 | var main = function() { 168 | var server_configs = {} 169 | , persist_configs = {} 170 | ; 171 | 172 | printStartupMsg(); 173 | processArguments(); 174 | 175 | // if app command included help flag then don't run app 176 | if (help) { 177 | process.exit(); 178 | } 179 | 180 | // if app command did not include help flag then start-up persist server 181 | else { 182 | server_configs = { 183 | "port": defaultPort, 184 | "secure": secure, 185 | "forceClose": forceClose, 186 | "ping": doPing, 187 | "pingInterval": pingIntervalTime, 188 | "closeTimeout": closeTimeout, 189 | "logLevel": logger.debugLevel 190 | } 191 | persist_configs = { 192 | "host": "localhost", 193 | "secure": secure, 194 | "port": defaultPort, 195 | "logLevel": logger.debugLevel 196 | } 197 | 198 | // create spacebrew server 199 | spacebrew.createServer( server_configs ); 200 | if (persist) persister.persistRoutes( persist_configs); 201 | } 202 | } 203 | 204 | /** 205 | * Run the app 206 | */ 207 | main(); 208 | -------------------------------------------------------------------------------- /admin/css/style.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: Din; 3 | font-weight: 100;/*lighter;*/ 4 | src: url("../fonts/DIN/DINNeuzeitGroteskStd-Light.otf") format("opentype"); 5 | } 6 | @font-face{ 7 | font-family: Din; 8 | font-weight: bold; 9 | src: url("../fonts/DIN/DINNeuzeitGroteskStd-BdCond.otf") format("opentype"); 10 | } 11 | @font-face{ 12 | font-family: DinPro; 13 | src: url("../fonts/DIN/DINPro-Regular.otf") format("opentype"); 14 | } 15 | @font-face{ 16 | font-family: DinPro; 17 | font-weight: bold; 18 | src: url("../fonts/DIN/DINPro-Bold.otf") format("opentype"); 19 | } 20 | @font-face{ 21 | font-family: DinPro; 22 | font-weight: 400;/*bolder;*/ 23 | src: url("../fonts/DIN/DINPro-Black.otf") format("opentype"); 24 | } 25 | @font-face{ 26 | font-family: DinPro; 27 | font-weight: 500; 28 | src: url("../fonts/DIN/DINPro-Medium.otf") format("opentype"); 29 | } 30 | body{ 31 | height:100%; 32 | background-color:#fff; 33 | /*background-image:url("../img/design/Spacebrew_0000_Default A notop.png");*/ 34 | } 35 | .container{ 36 | height:100%; 37 | margin-top:27px; 38 | margin-left:0px; 39 | margin-right:0px; 40 | } 41 | 42 | .navbar-fixed-bottom { 43 | position:fixed; 44 | right:0; 45 | left:0; 46 | z-index:1030; 47 | margin-bottom:0; 48 | bottom:0; 49 | } 50 | 51 | .navbar div{ 52 | float:left; 53 | font-family:Din; 54 | font-weight:bold; 55 | font-size:10pt; 56 | margin-top:2px; 57 | letter-spacing:1px; 58 | margin-right:28px; 59 | text-transform:uppercase; 60 | } 61 | 62 | .navbar .sig { 63 | font-size:6pt; 64 | float:right; 65 | margin-right:23px; 66 | margin-top:2px; 67 | color:#000; 68 | } 69 | 70 | .navbar .sig img{ 71 | vertical-align:middle; 72 | margin-top:-5px; 73 | margin-left:-9px; 74 | } 75 | 76 | .navbar div .prefix{ 77 | font-size:8pt; 78 | margin-right:6px; 79 | } 80 | 81 | .navbar{ 82 | height:50px; 83 | line-height:50px; 84 | color:#fff; 85 | overflow:visible; 86 | background-color:rgb(51,51,51); 87 | } 88 | 89 | .navbar .brand { 90 | display: block; 91 | float: left; 92 | font-size: 24px; 93 | font-family:DinPro; 94 | font-weight:bold; 95 | color:#999; 96 | text-decoration: none; 97 | letter-spacing:2px; 98 | margin-left:9px; 99 | margin-right:56px; 100 | } 101 | .navbar .brand img{ 102 | vertical-align:middle; 103 | margin-top:-10px; 104 | } 105 | .item.publisher{ 106 | padding-right:18px; 107 | background-color:rgb(176,237,255); 108 | text-align:right; 109 | } 110 | .item.subscriber{ 111 | padding-left:16px; 112 | background-color:rgb(177,255,216); 113 | } 114 | .key{ 115 | line-height:1; 116 | } 117 | .key div{ 118 | font-size:8pt; 119 | } 120 | .key .itemanchor{ 121 | height:9px; 122 | width:3px; 123 | margin-right:8px; 124 | } 125 | .itemanchor{ 126 | height:25px; 127 | width:55px; 128 | padding-right:10px; 129 | padding-left:10px; 130 | float:left; 131 | text-transform:uppercase; 132 | font-family:Din; 133 | font-weight:bold; 134 | color:white; 135 | font-size:7.5pt; 136 | text-align:center; 137 | letter-spacing:1px; 138 | background-color:black; 139 | } 140 | .subscriber .itemanchor{ 141 | float:right; 142 | } 143 | .string .itemanchor{ 144 | background-color:#ff0000; 145 | } 146 | .range .itemanchor{ 147 | background-color:#c600f8; 148 | } 149 | .boolean .itemanchor{ 150 | background-color:#ffae00; 151 | } 152 | .item.publisher[class*='sub_'], 153 | .item.connected.publisher{ 154 | background-color:rgb(168,182,255); 155 | text-align:right; 156 | } 157 | .item.subscriber[class*='pub_'], 158 | .item.connected.subscriber{ 159 | background-color:rgb(129,212,170); 160 | } 161 | .item{ 162 | line-height:25px; 163 | height:25px; 164 | /*margin:4px;*/ 165 | min-width:282px; 166 | font-family:DinPro; 167 | font-weight:500; 168 | font-size:10pt; 169 | /*-webkit-transition:all .4s ease-in-out;*/ 170 | pointer-events:none; 171 | } 172 | .itemwrapper{ 173 | padding:2px 4px 2px 4px; 174 | cursor:pointer; 175 | -webkit-touch-callout: none; 176 | -webkit-user-select: none; 177 | -khtml-user-select: none; 178 | -moz-user-select: none; 179 | -ms-user-select: none; 180 | user-select: none; 181 | } 182 | .publisher.itemwrapper{ 183 | padding-right:8px; 184 | padding-left:0px; 185 | margin-left:4px; 186 | margin-right:-4px; 187 | } 188 | .subscriber.itemwrapper{ 189 | padding-right:0px; 190 | padding-left:8px; 191 | margin-left:-4px; 192 | margin-right:4px; 193 | } 194 | .pubsubsep{ 195 | height:100%; 196 | position:absolute; 197 | margin-left:-36px; 198 | border-right:1px dashed black; 199 | } 200 | .clientrow{ 201 | margin-bottom:53px; 202 | overflow:hidden; 203 | min-width:973px; 204 | position:relative; 205 | } 206 | .clientrow:hover, 207 | .clientrow.selected{ 208 | background-image:url("../img/dot-bg.png"); 209 | } 210 | .clientanchor{ 211 | position:absolute; 212 | width:17px; 213 | height:100%; 214 | background-color:rgb(230,230,230); 215 | } 216 | .client{ 217 | 218 | } 219 | .clientdata{ 220 | padding-top:5px; 221 | padding-bottom:4px; 222 | } 223 | .clientnickname{ 224 | font-family:DinPro; 225 | font-weight:bold; 226 | font-size:13pt; 227 | } 228 | .clientname, 229 | .clientinfo{ 230 | font-family:DinPro; 231 | font-size:9pt; 232 | /*margin-top:-3px;*/ 233 | } 234 | .clientnickname span, 235 | .clientname span, 236 | .clientinfo span{ 237 | display:inline-block; 238 | background-color:white; 239 | padding-left:3px; 240 | margin-left:-3px; 241 | } 242 | .clientcol{ 243 | float:left; 244 | width:231px; 245 | margin-left:35px; 246 | overflow:hidden; 247 | } 248 | .pubcol{ 249 | float:left; 250 | } 251 | .subcol{ 252 | float:left; 253 | } 254 | .pubdata{ 255 | margin-right:93px; 256 | min-width:308px; 257 | } 258 | ._jsPlumb_endpoint{ 259 | z-index:5; 260 | pointer-events:none; 261 | } 262 | .headerrow{ 263 | margin-bottom:29px; 264 | margin-left:14px; 265 | overflow:hidden; 266 | min-width:940px; 267 | } 268 | .header{ 269 | font-family:DinPro; 270 | font-size:18pt; 271 | letter-spacing:2px; 272 | text-transform:uppercase; 273 | } 274 | #clientdataheader{ 275 | margin-right:144px; 276 | margin-left:6px; 277 | } 278 | #pubdataheader{ 279 | margin-right:85px; 280 | } 281 | .header .prefix{ 282 | font-size:9pt; 283 | margin-right:7px; 284 | } 285 | .publisher .deletebutton{ 286 | float:left; 287 | border-right:solid white 2px; 288 | } 289 | .subscriber .deletebutton{ 290 | float:right; 291 | border-left:solid white 2px; 292 | } 293 | .deletebutton{ 294 | display:none; 295 | pointer-events:auto; 296 | width:20px; 297 | text-align: center; 298 | background-color:black; 299 | color:white; 300 | font-weight:bolder; 301 | font-family:DinPro; 302 | } 303 | .deletebutton:hover{ 304 | color:red; 305 | } 306 | .clientinfo{ 307 | display:none; 308 | } 309 | .clientinfo.show{ 310 | display:block; 311 | } 312 | .clientname span.infobutton{ 313 | font-family:DinPro; 314 | font-weight:bolder; 315 | color:black; 316 | background-color:#FFBE00; 317 | font-size:9pt; 318 | width:10px; 319 | margin-left:2px; 320 | cursor:pointer; 321 | display:none; 322 | } 323 | .clientinfo span{ 324 | background-color:yellow; 325 | margin-right:10px; 326 | } 327 | 328 | div.links{ 329 | cursor: hand; cursor: pointer; 330 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spacebrew Server 2 | ================ 3 | 4 | A dynamically re-routable software toolkit for choreographing interactive spaces. Visit http://www.spacebrew.cc to learn more about spacebrew. On our site we feature a bunch of example apps and tutorials to help you get started. You'll also find a blog where we feature spacebrew projects and events. 5 | 6 | @version: 0.4.0 7 | @date: April 10, 2014 8 | @contributors: LAB at Rockwell Group, Quin Kennedy, Brett Renfer, Josh Walton, James Tichenor, Julio Terra 9 | 10 | Getting Started 11 | --------------- 12 | 13 | ### 1. Install Dependencies 14 | * Download and install [Node.js](http://nodejs.org) 15 | * Clone the repo from github 16 | * Install the dependencies using node packaged modules 17 | - `npm install` 18 | 19 | ### 2. Run the Server 20 | * Open terminal and navigate to the base directory of the spacebrew server 21 | * Run the server by using `node node_server_forever.js` 22 | 23 | `node_server_forever.js` vs `node_server.js` 24 | The first of these two files runs node using the forever-monitor node utility. This utility relaunches the spacebrew server if it crashes and it saves logs of the standard output from the spacebrew server to log files in the data/logs directory. 25 | 26 | ### 3. Connect Client Apps 27 | * Open the [spacebrew_button example](http://spacebrew.github.io/spacebrew.js/spacebrew_button/index.html?server=localhost&name=button2) - make sure that the `server=` in the query string points to the appropriate host. Customize the `name=` element in the query string to change your apps name. 28 | * Open the [spacebrew admin interface](http://spacebrew.github.io/spacebrew/admin/admin.html?server=localhost) in another browser window - again, make sure that the `server=` in the query string points to the appropriate host. 29 | * Start connecting apps and routing data. 30 | 31 | Spacebrew Server Options 32 | ------------------------ 33 | Here is an overview of the command line options that the spacebrew server accepts: 34 | ``` 35 | --port (-p): set the port of the spacebrew server (default 9000) 36 | --help (-h): print help text (which is what you are reading here) 37 | --close (-c): force close clients that don't respond to pings 38 | --ping: enable pinging of clients to track who is potentially disconnected (default) 39 | --noping: opposite of --ping 40 | --timeout (-t): minimum number of ms to wait for response pong before force closing (implies --close, default 10000 [10 seconds]) 41 | --persist: saves route configurations that are set via any admin interface 42 | --nopersist: opposite of --persist 43 | --log (-l): sets logging to info level 44 | --loglevel: set logging to info, debug, warn, error 45 | --pinginterval: the number of ms between pings (implies --ping, default 1000 [1 second]) 46 | --secure: launch the server with https/wss support. See certificate details in the secure/ directory. 47 | ``` 48 | 49 | Here are a few examples of how to launch the app using command line options: 50 | ``` 51 | node node_forever_server.js -p 9011 -t 1000 --pinginterval 1000 52 | node node_server.js --nopersist --loglevel warn 53 | ``` 54 | 55 | Other Services 56 | -------------- 57 | 58 | ### HTTP Link 59 | 60 | The HTTP Link (`http_link.js`) is a Node.js app which acts essentially as an HTTP <-> Websocket bridge for Spacebrew. Only `GET` requests are supported currently, so all commands are read from the query string. Responses are provided as JSON. 61 | 62 | The HTTP Link allows you to use HTTP-only devices, such as the [Electric Imp](http://electricimp.com/), within the Spacebrew environment. 63 | 64 | 1. Register a client by sending a `config` query string key which contains the same json structure as would be sent over Websockets 65 | - `http://localhost:9092/?config={"config":{"name":"test","publish":{"messages":[{"name":"output","type":"string"},{"name":"out","type":"string"}]},"subscribe":{"messages":[{"name":"input","type":"string"}]}}}` 66 | - this is the human-readable version, don't forget to URL encode the data first 67 | * The HTTP Link will respond with a `clientID` that you will use in the future to refer your client. 68 | * You can send messages into the Spacebrew environment by sending a `publish` query string key which contains an array of messages you wish to publish 69 | - `http://localhost:9092/?clientID=0&publish=[{"message":{"clientName":"test","name":"output","type":"string","value":"hello!"}},{"message":{"clientName":"test","name":"output","type":"string","value":"good bye."}}]` 70 | - in this case we are sending 2 messages 71 | * You can retrieve sent messages by including `poll=true` in the query string. This will return an array of all messages that have been received by the HTTP Link for your client since the last poll: 72 | - `http://localhost:9092/?clientID=0&poll=true` 73 | * By default, only one message is queued per subscriber. If you wish to queue more, you can send a `bufferSize` along with your subscriber specifications 74 | - `http://localhost:9092/?clientID=0&config={"config":{"name":"test","publish":{"messages":[{"name":"output","type":"string"},{"name":"out","type":"string"}]},"subscribe":{"messages":[{"name":"input","type":"string","bufferSize":3}]}}}` 75 | - this example also shows how you can send a config update 76 | * By default the HTTP Link will remove your client after 5 minutes if there is no queries associated with it. You can change this at any time by specifying a custom `timeout` in seconds 77 | - `http://localhost:9092/?clientID=0&poll=true&timeout=3600` 78 | - this example polls for input and also sets a 1-hour timeout 79 | 80 | ### Command Line Persistent Admin 81 | 82 | The Persistent Admin (`node_persistent_admin.js`) is a command line Node.js app which makes sure certain specified publishers and subscribers always stay routed to one-another. 83 | 84 | After starting the Persistent Admin (`node node_persistent_admin.js` in the command line/terminal) you can type `help` to get an overview of the various commands available. Basically you can: 85 | 86 | * `ls` to get a list of currently-protected routes 87 | * `add myClient,pubOne,theirClient,subscriberUno` to connect the `pubOne` publisher associated with client `myClient` to the `subscriberUno` subscriber associated with client `theirClient` 88 | * `save` to save to disk 89 | * `load` to load from disk (it will automatically load when starting up) 90 | * `remove 0` to remove the zero'th route from protection (when you list the routes via `ls`, the indices listed before each route are what should be used for the remove command) 91 | * `exit` to quit the Persistent Admin 92 | * the `add` command can also be used with regular expressions such as `add myClient,.*,theirClient,.*` to connect all publishers from `myClient` with all compatible subscribers in `theirClient` 93 | 94 | 95 | ============= 96 | #### LICENSE 97 | The MIT License (MIT) 98 | Copyright © 2012 LAB at Rockwell Group, http://www.rockwellgroup.com/lab 99 | 100 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 101 | 102 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 103 | 104 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 105 | -------------------------------------------------------------------------------- /admin/js/jsplumb/jsPlumb-util-1.3.13-RC1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jsPlumb 3 | * 4 | * Title:jsPlumb 1.3.13 5 | * 6 | * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas 7 | * elements, or VML. 8 | * 9 | * This file contains the util functions 10 | * 11 | * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) 12 | * 13 | * http://jsplumb.org 14 | * http://github.com/sporritt/jsplumb 15 | * http://code.google.com/p/jsplumb 16 | * 17 | * Dual licensed under the MIT and GPL2 licenses. 18 | */ 19 | jsPlumbUtil = { 20 | isArray : function(a) { 21 | return Object.prototype.toString.call(a) === "[object Array]"; 22 | }, 23 | isString : function(s) { 24 | return typeof s === "string"; 25 | }, 26 | isBoolean: function(s) { 27 | return typeof s === "boolean"; 28 | }, 29 | isObject : function(o) { 30 | return Object.prototype.toString.call(o) === "[object Object]"; 31 | }, 32 | merge : function(a, b) { 33 | var c = jsPlumb.extend({}, a); 34 | for (var i in b) { 35 | if (c[i] == null || this.isString(b[i]) || this.isBoolean(b[i])) 36 | c[i] = b[i]; 37 | else { 38 | if (this.isArray(b[i]) && this.isArray(c[i])) { 39 | var ar = []; 40 | ar.push.apply(ar, c[i]); 41 | ar.push.apply(ar, b[i]); 42 | c[i] = ar; 43 | } 44 | else if(this.isObject(c[i]) && this.isObject(b[i])) { 45 | for (var j in b[i]) 46 | c[i][j] = b[i][j]; 47 | } 48 | } 49 | } 50 | return c; 51 | }, 52 | convertStyle : function(s, ignoreAlpha) { 53 | // TODO: jsPlumb should support a separate 'opacity' style member. 54 | if ("transparent" === s) return s; 55 | var o = s, 56 | pad = function(n) { return n.length == 1 ? "0" + n : n; }, 57 | hex = function(k) { return pad(Number(k).toString(16)); }, 58 | pattern = /(rgb[a]?\()(.*)(\))/; 59 | if (s.match(pattern)) { 60 | var parts = s.match(pattern)[2].split(","); 61 | o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); 62 | if (!ignoreAlpha && parts.length == 4) 63 | o = o + hex(parts[3]); 64 | } 65 | return o; 66 | }, 67 | gradient : function(p1, p2) { 68 | p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; 69 | p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; 70 | return (p2[1] - p1[1]) / (p2[0] - p1[0]); 71 | }, 72 | normal : function(p1, p2) { 73 | return -1 / jsPlumbUtil.gradient(p1,p2); 74 | }, 75 | lineLength : function(p1, p2) { 76 | p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; 77 | p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; 78 | return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); 79 | }, 80 | segment : function(p1, p2) { 81 | p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; 82 | p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; 83 | if (p2[0] > p1[0]) { 84 | return (p2[1] > p1[1]) ? 2 : 1; 85 | } 86 | else { 87 | return (p2[1] > p1[1]) ? 3 : 4; 88 | } 89 | }, 90 | intersects : function(r1, r2) { 91 | var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, 92 | a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; 93 | 94 | return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || 95 | ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || 96 | ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || 97 | ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || 98 | 99 | ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || 100 | ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || 101 | ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || 102 | ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); 103 | }, 104 | segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], 105 | inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], 106 | pointOnLine : function(fromPoint, toPoint, distance) { 107 | var m = jsPlumbUtil.gradient(fromPoint, toPoint), 108 | s = jsPlumbUtil.segment(fromPoint, toPoint), 109 | segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], 110 | theta = Math.atan(m), 111 | y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], 112 | x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; 113 | return { x:fromPoint.x + x, y:fromPoint.y + y }; 114 | }, 115 | /** 116 | * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. 117 | * @param fromPoint 118 | * @param toPoint 119 | * @param length 120 | */ 121 | perpendicularLineTo : function(fromPoint, toPoint, length) { 122 | var m = jsPlumbUtil.gradient(fromPoint, toPoint), 123 | theta2 = Math.atan(-1 / m), 124 | y = length / 2 * Math.sin(theta2), 125 | x = length / 2 * Math.cos(theta2); 126 | return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; 127 | }, 128 | findWithFunction : function(a, f) { 129 | if (a) 130 | for (var i = 0; i < a.length; i++) if (f(a[i])) return i; 131 | return -1; 132 | }, 133 | indexOf : function(l, v) { 134 | return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); 135 | }, 136 | removeWithFunction : function(a, f) { 137 | var idx = jsPlumbUtil.findWithFunction(a, f); 138 | if (idx > -1) a.splice(idx, 1); 139 | return idx != -1; 140 | }, 141 | remove : function(l, v) { 142 | var idx = jsPlumbUtil.indexOf(l, v); 143 | if (idx > -1) l.splice(idx, 1); 144 | return idx != -1; 145 | }, 146 | // TODO support insert index 147 | addWithFunction : function(list, item, hashFunction) { 148 | if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); 149 | }, 150 | addToList : function(map, key, value) { 151 | var l = map[key]; 152 | if (l == null) { 153 | l = [], map[key] = l; 154 | } 155 | l.push(value); 156 | return l; 157 | }, 158 | /** 159 | * EventGenerator 160 | * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. 161 | */ 162 | EventGenerator : function() { 163 | var _listeners = {}, self = this; 164 | 165 | // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to 166 | // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event 167 | // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" 168 | // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting 169 | // to hear what other people think. 170 | var eventsToDieOn = [ "ready" ]; 171 | 172 | /* 173 | * Binds a listener to an event. 174 | * 175 | * Parameters: 176 | * event - name of the event to bind to. 177 | * listener - function to execute. 178 | */ 179 | this.bind = function(event, listener) { 180 | jsPlumbUtil.addToList(_listeners, event, listener); 181 | return self; 182 | }; 183 | /* 184 | * Fires an update for the given event. 185 | * 186 | * Parameters: 187 | * event - event to fire 188 | * value - value to pass to the event listener(s). 189 | * originalEvent - the original event from the browser 190 | */ 191 | this.fire = function(event, value, originalEvent) { 192 | if (_listeners[event]) { 193 | for ( var i = 0; i < _listeners[event].length; i++) { 194 | // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this 195 | // method will have the whole call stack available in the debugger. 196 | if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) 197 | _listeners[event][i](value, originalEvent); 198 | else { 199 | // for events we don't want to die on, catch and log. 200 | try { 201 | _listeners[event][i](value, originalEvent); 202 | } catch (e) { 203 | jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); 204 | } 205 | } 206 | } 207 | } 208 | return self; 209 | }; 210 | /* 211 | * Clears either all listeners, or listeners for some specific event. 212 | * 213 | * Parameters: 214 | * event - optional. constrains the clear to just listeners for this event. 215 | */ 216 | this.unbind = function(event) { 217 | if (event) 218 | delete _listeners[event]; 219 | else { 220 | _listeners = {}; 221 | } 222 | return self; 223 | }; 224 | 225 | this.getListener = function(forEvent) { 226 | return _listeners[forEvent]; 227 | }; 228 | }, 229 | logEnabled : true, 230 | log : function() { 231 | if (jsPlumbUtil.logEnabled && typeof console != "undefined") { 232 | try { 233 | var msg = arguments[arguments.length - 1]; 234 | console.log(msg); 235 | } 236 | catch (e) {} 237 | } 238 | }, 239 | group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, 240 | groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, 241 | time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, 242 | timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } 243 | }; -------------------------------------------------------------------------------- /admin/js/wsevents.js: -------------------------------------------------------------------------------- 1 | var name = gup('name') || window.location.href; 2 | var server = gup('server') || 'localhost'; 3 | var port = gup('port') || '9000'; 4 | var debug = gup('debug') || false; 5 | 6 | var ws; 7 | 8 | var reconnect_timer = undefined; 9 | 10 | var setupWebsocket = function(){ 11 | //detect https automagically 12 | var protocol = ('https:' == document.location.protocol ? 'wss://' : 'ws://'); 13 | ws = new WebSocket(protocol + server + ":" + Number(port)); 14 | 15 | ws.onopen = function() { 16 | console.log("WebSockets connection opened"); 17 | var adminMsg = { "admin": [ 18 | {"admin": true} 19 | ]}; 20 | ws.send(JSON.stringify(adminMsg)); 21 | 22 | /////////////////////////////////////////// 23 | // ADMIN RECONNECT FUNCTIONALITY 24 | if (reconnect_timer) { 25 | console.log("[ws.onopen] reconnected successfully - clearing timer"); 26 | reconnect_timer = clearTimeout(reconnect_timer); 27 | reconnect_timer = undefined; 28 | } 29 | /////////////////////////////////////////// 30 | 31 | }; 32 | 33 | ws.onmessage = function(e) { 34 | //if (debug) console.log("Got WebSockets message: " + e.data); 35 | if (debug) console.log("Got WebSockets message:"); 36 | if (debug) console.log(e); 37 | //try { 38 | var json = JSON.parse(e.data); 39 | if (!handleMsg(json)){ 40 | for(var i = 0, end = json.length; i < end; i++){ 41 | handleMsg(json[i]); 42 | } 43 | } 44 | // } catch (err) { 45 | // if (debug) console.log('This doesn\'t look like a valid JSON: ', e.data); 46 | // return; 47 | // } 48 | }; 49 | 50 | ws.onclose = function() { 51 | console.log("[ws.onclose] WebSockets connection closed"); 52 | 53 | /////////////////////////////////////////// 54 | // ADMIN RECONNECT FUNCTIONALITY 55 | if (!reconnect_timer) { 56 | reconnect_timer = setInterval(function() { 57 | console.log("[reconnect_timer] attempting to reconnect to spacebrew"); 58 | removeAllClients(); 59 | setupWebsocket(); 60 | }, 5000); 61 | } 62 | /////////////////////////////////////////// 63 | }; 64 | }; 65 | 66 | var clients = []; 67 | var routes = []; 68 | 69 | var handleMsg = function(json){ 70 | if (json.config){ 71 | handleConfigMsg(json); 72 | } else if (json.message){ 73 | handleMessageMsg(json); 74 | } else if (json.route){ 75 | handleRouteMsg(json); 76 | } else if (json.remove){ 77 | handleRemoveMsg(json); 78 | } else if (json.admin){ 79 | //do nothing 80 | } else { 81 | return false; 82 | } 83 | return true; 84 | }; 85 | 86 | var handleMessageMsg = function(msg){ 87 | // for(var i = clients.length - 1; i >= 0; i--){ 88 | // if (clients[i].name === msg.message.clientName 89 | // && clients[i].remoteAddress === msg.message.remoteAddress){ 90 | // break; 91 | // } 92 | // } 93 | // var selector2 = "input[name=pub][value='{name}_{addr}_{pubName}_{pubType}']:radio".replace("{name}",.Safetify()).replace("{addr}", msg.message.remoteAddress.Safetify()).replace("{pubName}",msg.message.name.Safetify()).replace("{pubType}",msg.message.type.Safetify()); 94 | // $(selector2).parent().addClass('active'); 95 | var itemSelector = getCommItemSelector(true, msg.message.clientName, msg.message.remoteAddress, msg.message.name, msg.message.type); 96 | var fromEndpoint = myPlumb.endpoints[itemSelector]; 97 | if (fromEndpoint){ 98 | var getImage = function(active){ 99 | return "img/node-"+($("#"+itemSelector).attr('class').indexOf('sub_') < 0 100 | ? "open" 101 | : "closed")+(active ? "-active-i" : "") + ".png"; 102 | }; 103 | fromEndpoint.setImage(getImage(true));//.setPaintStyle(myPlumb.endpointActiveStyle); 104 | setTimeout(function(){ 105 | fromEndpoint.setImage(getImage(false));/*setPaintStyle(myPlumb.endpointPaintStyle);*/ 106 | },200); 107 | } 108 | }; 109 | 110 | var commSelectorTemplate = Handlebars.compile("{{pub}}_{{Safetify clientName}}_{{Safetify remoteAddress}}_{{Safetify name}}_{{Safetify type}}"); 111 | var getCommItem = function(a_bPublisher, a_sClientName, a_sRemoteAddress, a_sName, a_sType){ 112 | return $("#"+getCommItemSelector.apply(this, arguments)); 113 | }; 114 | 115 | var getCommItemSelector = function(a_bPublisher, a_sClientName, a_sRemoteAddress, a_sName, a_sType){ 116 | return commSelectorTemplate({ pub: (a_bPublisher?"pub":"sub"), 117 | clientName: a_sClientName, 118 | remoteAddress: a_sRemoteAddress, 119 | name: a_sName, 120 | type: a_sType}); 121 | }; 122 | 123 | var routeTemplate; 124 | routeTemplate = Handlebars.compile(document.getElementById( 'route_handlebar' ).textContent); 125 | var clientTemplate; 126 | clientTemplate = Handlebars.compile(document.getElementById( 'client_handlebar' ).textContent); 127 | var pubsubTemplate; 128 | pubsubTemplate = Handlebars.compile(document.getElementById( 'pubsub_handlebar' ).textContent); 129 | 130 | var displayRoutes = function(){ 131 | $("#route_list").html(routeTemplate({routes:routes})); 132 | }; 133 | 134 | var addEndpoints = function(msg){ 135 | var clientName = msg.config.name, 136 | remoteAddress = msg.config.remoteAddress, 137 | i,endpoint,currM,id; 138 | if (msg.config.publish && msg.config.publish.messages){ 139 | i = msg.config.publish.messages.length; 140 | while (i--){ 141 | currM = msg.config.publish.messages[i]; 142 | id = getCommItemSelector(true, clientName, remoteAddress, currM.name, currM.type); 143 | endpoint = jsPlumb.addEndpoint(id, myPlumb.sourceEndpoint); 144 | myPlumb.endpoints[id] = endpoint; 145 | } 146 | } 147 | if (msg.config.subscribe && msg.config.subscribe.messages){ 148 | i = msg.config.subscribe.messages.length; 149 | while(i--){ 150 | currM = msg.config.subscribe.messages[i]; 151 | id = getCommItemSelector(false, clientName, remoteAddress, currM.name, currM.type); 152 | endpoint = jsPlumb.addEndpoint(id, myPlumb.targetEndpoint); 153 | myPlumb.endpoints[id] = endpoint; 154 | } 155 | } 156 | }; 157 | 158 | var handleConfigMsg = function(msg){ 159 | for(var j = 0; j < clients.length; j++){ 160 | if (clients[j].name === msg.config.name 161 | && clients[j].remoteAddress === msg.config.remoteAddress){ 162 | //TODO: if the client already has a config, lets cleanup 163 | //the old endpoints and old markup 164 | clients[j].config = msg.config; 165 | var itemsMarkup = $(pubsubTemplate(clients[j])); 166 | itemsMarkup.find(".itemwrapper").click(clickItem).hover(overItem, outItem); 167 | //itemsMarkup.find(".deletebutton").click(clickDelete); 168 | var client = $("#"+msg.config.name.Safetify()+"_"+msg.config.remoteAddress.Safetify()); 169 | client.append(itemsMarkup); 170 | addEndpoints(msg); 171 | //update the description 172 | if (msg.config.description){ 173 | var idPart ="info_"+msg.config.name.Safetify()+"_"+msg.config.remoteAddress.Safetify(); 174 | $("#button_"+idPart).css("display","inline-block"); 175 | $("#"+idPart+" span").html(msg.config.description); 176 | client.find(".clientnickname, .clientname").attr("title",msg.config.description); 177 | } 178 | return; 179 | } 180 | } 181 | 182 | //if we did not find a matching client, then add this one 183 | var newClient = {name:msg.config.name, remoteAddress:msg.config.remoteAddress}; 184 | var clientMarkup = $(clientTemplate(newClient)); 185 | clientMarkup.find(".infobutton").click(clickInfo); 186 | $("#client_list").append(clientMarkup); 187 | clients.push(newClient); 188 | //and then updated it with the additional info. 189 | handleConfigMsg(msg); 190 | }; 191 | 192 | var removeClient = function(client){ 193 | var clientName = client.name, 194 | remoteAddress = client.remoteAddress, 195 | name, type; 196 | $("#"+clientName.Safetify()+"_"+remoteAddress.Safetify()).remove(); 197 | 198 | if (client.config && client.config.publish && client.config.publish.messages){ 199 | for(var i = 0; i < client.config.publish.messages.length; i++){ 200 | name = client.config.publish.messages[i].name; 201 | type = client.config.publish.messages[i].type; 202 | jsPlumb.deleteEndpoint(myPlumb.endpoints[["pub",clientName, remoteAddress, name, type].map(Safetify).join("_")]); 203 | } 204 | } 205 | if (client.config && client.config.subscribe && client.config.subscribe.messages){ 206 | for(var i = 0; i < client.config.subscribe.messages.length; i++){ 207 | name = client.config.subscribe.messages[i].name; 208 | type = client.config.subscribe.messages[i].type; 209 | jsPlumb.deleteEndpoint(myPlumb.endpoints[["sub",clientName, remoteAddress, name, type].map(Safetify).join("_")]); 210 | } 211 | } 212 | }; 213 | 214 | var addConnection = function(msg){ 215 | var item = msg.route.publisher; 216 | var sourceid = getCommItemSelector(true, item.clientName, item.remoteAddress, item.name, item.type); 217 | item = msg.route.subscriber; 218 | var targetid = getCommItemSelector(false, item.clientName, item.remoteAddress, item.name, item.type); 219 | var source = myPlumb.endpoints[sourceid]; 220 | var target = myPlumb.endpoints[targetid]; 221 | source.setImage("img/node-closed.png"); 222 | target.setImage("img/node-closed.png"); 223 | if (!myPlumb.connections[sourceid]){ 224 | myPlumb.connections[sourceid] = {}; 225 | } 226 | if (!myPlumb.connections[sourceid][targetid]){ 227 | var connection = jsPlumb.connect({source:source,target:target}, myPlumb.connectionParams); 228 | myPlumb.connections[sourceid][targetid] = connection; 229 | } 230 | handleSelecting(sourceid, targetid); 231 | }; 232 | 233 | var handleSelecting = function(pubId, subId){ 234 | $("#"+subId).addClass(pubId); 235 | $("#"+pubId).addClass(subId); 236 | // if (currState == PUB_SELECTED){ 237 | // if (pubId == $(".publisher.selected").prop('id')){ 238 | // $("#"+subId).addClass("selected"); 239 | // } 240 | // } else if (currState == SUB_SELECTED){ 241 | // if (subId == $(".subscriber.selected").prop('id')){ 242 | // $("#"+pubId).addClass("selected"); 243 | // } 244 | // } 245 | }; 246 | 247 | var handleUnselecting = function(pubId, subId){ 248 | var subscriber = $("#"+subId); 249 | var publisher = $("#"+pubId); 250 | subscriber.removeClass(pubId); 251 | publisher.removeClass(subId); 252 | if (subscriber.attr('class').indexOf('pub_') < 0){ 253 | myPlumb.endpoints[subId].setImage("img/node-open.png"); 254 | } 255 | if (publisher.attr('class').indexOf('sub_') < 0){ 256 | myPlumb.endpoints[pubId].setImage("img/node-open.png"); 257 | } 258 | }; 259 | 260 | var removeConnection = function(msg){ 261 | var item = msg.route.publisher; 262 | var sourceid = getCommItemSelector(true, item.clientName, item.remoteAddress, item.name, item.type); 263 | item = msg.route.subscriber; 264 | var targetid = getCommItemSelector(false, item.clientName, item.remoteAddress, item.name, item.type); 265 | if (myPlumb.connections[sourceid] && myPlumb.connections[sourceid][targetid]){ 266 | jsPlumb.detach(myPlumb.connections[sourceid][targetid]); 267 | handleUnselecting(sourceid, targetid); 268 | myPlumb.connections[sourceid][targetid] = undefined; 269 | } 270 | }; 271 | 272 | var handleRouteMsg = function(msg){ 273 | if (msg.route.type === 'add'){ 274 | routes.push({publisher:msg.route.publisher, 275 | subscriber:msg.route.subscriber}); 276 | addConnection(msg); 277 | } else if (msg.route.type === 'remove'){ 278 | for(var i = routes.length - 1; i >= 0; i--){ 279 | var myPub = routes[i].publisher; 280 | var thePub = msg.route.publisher; 281 | var mySub = routes[i].subscriber; 282 | var theSub = msg.route.subscriber; 283 | if (myPub.clientName === thePub.clientName 284 | && myPub.name === thePub.name 285 | && myPub.type === thePub.type 286 | && myPub.remoteAddress === thePub.remoteAddress 287 | && mySub.clientName === theSub.clientName 288 | && mySub.name === theSub.name 289 | && mySub.type === theSub.type 290 | && mySub.remoteAddress === theSub.remoteAddress){ 291 | removeConnection(msg); 292 | routes.splice(i, 1); 293 | } 294 | } 295 | } 296 | displayRoutes(); 297 | }; 298 | 299 | var handleRemoveMsg = function(msg){ 300 | //for each entry in the remove list 301 | //for each entry in the clients list 302 | //if the name & address match, then remove it from the list 303 | for(var i = 0; i < msg.remove.length; i++){ 304 | for(var j = 0; j < clients.length; j++){ 305 | if (clients[j].name === msg.remove[i].name 306 | && clients[j].remoteAddress === msg.remove[i].remoteAddress){ 307 | removeClient(clients.splice(j, 1)[0]); 308 | break; 309 | } 310 | } 311 | } 312 | }; 313 | 314 | /////////////////////////////////////////// 315 | // ADMIN RECONNECT FUNCTIONALITY 316 | var removeAllClients = function(){ 317 | for(var j = clients.length - 1; j >= 0; j--){ 318 | removeClient(clients[j]); 319 | } 320 | clients = []; 321 | routes = []; 322 | }; 323 | ///////////////////////////////////////////////// -------------------------------------------------------------------------------- /admin/js/jsplumb/yui.jsPlumb-1.3.13-RC1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jsPlumb 3 | * 4 | * Title:jsPlumb 1.3.13 5 | * 6 | * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas 7 | * elements, or VML. 8 | * 9 | * This file contains the YUI3 adapter. 10 | * 11 | * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) 12 | * 13 | * http://jsplumb.org 14 | * http://github.com/sporritt/jsplumb 15 | * http://code.google.com/p/jsplumb 16 | * 17 | * Dual licensed under the MIT and GPL2 licenses. 18 | */ 19 | 20 | /** 21 | * addClass adds a class to the given element 22 | * animate calls the underlying library's animate functionality 23 | * appendElement appends a child element to a parent element. 24 | * bind binds some event to an element 25 | * dragEvents a dictionary of event names 26 | * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. 27 | * getAttribute gets some attribute from an element 28 | * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback 29 | * getDragScope gets the drag scope for a given element. 30 | * getElementObject turns an id or dom element into an element object of the underlying library's type. 31 | * getOffset gets an element's offset 32 | * getOriginalEvent gets the original browser event from some wrapper event. 33 | * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? 34 | * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? 35 | * getSize gets an element's size. 36 | * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. 37 | * initDraggable initializes an element to be draggable 38 | * initDroppable initializes an element to be droppable 39 | * isDragSupported returns whether or not drag is supported for some element. 40 | * isDropSupported returns whether or not drop is supported for some element. 41 | * removeClass removes a class from a given element. 42 | * removeElement removes some element completely from the DOM. 43 | * setAttribute sets an attribute on some element. 44 | * setDraggable sets whether or not some element should be draggable. 45 | * setDragScope sets the drag scope for a given element. 46 | * setOffset sets the offset of some element. 47 | */ 48 | (function() { 49 | 50 | if (!Array.prototype.indexOf) { 51 | Array.prototype.indexOf = function( v, b, s ) { 52 | for( var i = +b || 0, l = this.length; i < l; i++ ) { 53 | if( this[i]===v || s && this[i]==v ) { return i; } 54 | } 55 | return -1; 56 | }; 57 | } 58 | 59 | var Y; 60 | 61 | YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { 62 | Y = _Y; 63 | Y.on("domready", function() { jsPlumb.init(); }); 64 | }); 65 | 66 | /** 67 | * adds the given value to the given list, with the given scope. creates the scoped list 68 | * if necessary. 69 | * used by initDraggable and initDroppable. 70 | */ 71 | var _add = function(list, scope, value) { 72 | var l = list[scope]; 73 | if (!l) { 74 | l = []; 75 | list[scope] = l; 76 | } 77 | l.push(value); 78 | }, 79 | ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", 80 | "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", 81 | "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", 82 | "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" 83 | ], 84 | animEvents = [ "tween" ], 85 | /** 86 | * helper function to curry callbacks for some element. 87 | */ 88 | _wrapper = function(fn) { 89 | return function() { 90 | try { 91 | return fn.apply(this, arguments); 92 | } 93 | catch (e) { } 94 | }; 95 | }, 96 | /** 97 | * extracts options from the given options object, leaving out event handlers. 98 | */ 99 | _getDDOptions = function(options) { 100 | var o = {}; 101 | for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; 102 | return o; 103 | }, 104 | /** 105 | * attaches all event handlers found in options to the given dragdrop object, and registering 106 | * the given el as the element of interest. 107 | */ 108 | _attachListeners = function(dd, options, eventList) { 109 | for (var ev in options) { 110 | if (eventList.indexOf(ev) != -1) { 111 | var w = _wrapper(options[ev]); 112 | dd.on(ev, w); 113 | } 114 | } 115 | }, 116 | _droppables = {}, 117 | _droppableOptions = {}, 118 | _draggablesByScope = {}, 119 | _draggablesById = {}, 120 | _droppableScopesById = {}, 121 | _checkHover = function(el, entering) { 122 | if (el) { 123 | var id = el.get("id"); 124 | if (id) { 125 | var options = _droppableOptions[id]; 126 | if (options) { 127 | if (options['hoverClass']) { 128 | if (entering) el.addClass(options['hoverClass']); 129 | else el.removeClass(options['hoverClass']); 130 | } 131 | } 132 | } 133 | } 134 | }, 135 | _lastDragObject = null, 136 | _extend = function(o1, o2) { 137 | for (var i in o2) 138 | o1[i] = o2[i]; 139 | return o1; 140 | }, 141 | _getAttribute = function(el, attributeId) { 142 | return el.getAttribute(attributeId); 143 | }, 144 | _getElementObject = function(el) { 145 | if (el == null) return null; 146 | var eee = null; 147 | eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); 148 | return eee; 149 | }; 150 | 151 | jsPlumb.CurrentLibrary = { 152 | 153 | addClass : function(el, clazz) { 154 | jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); 155 | }, 156 | 157 | /** 158 | * animates the given element. 159 | */ 160 | animate : function(el, properties, options) { 161 | var o = _extend({node:el, to:properties}, options), 162 | id = _getAttribute(el, "id"); 163 | o["tween"] = jsPlumb.wrap(properties["tween"], function() { 164 | // TODO should use a current instance. 165 | jsPlumb.repaint(id); 166 | }); 167 | var a = new Y.Anim(o); 168 | _attachListeners(a, o, animEvents); 169 | a.run(); 170 | }, 171 | 172 | appendElement : function(child, parent) { 173 | _getElementObject (parent).append(child); 174 | }, 175 | 176 | /** 177 | * event binding wrapper. 178 | */ 179 | bind : function(el, event, callback) { 180 | _getElementObject(el).on(event, callback); 181 | }, 182 | 183 | dragEvents : { 184 | "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", 185 | "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" 186 | }, 187 | 188 | extend : _extend, 189 | 190 | getAttribute : _getAttribute, 191 | 192 | getClientXY : function(eventObject) { 193 | return [eventObject.clientX, eventObject.clientY]; 194 | }, 195 | 196 | /** 197 | * takes the args passed to an event function and returns you an object representing that which is being dragged. 198 | */ 199 | getDragObject : function(eventArgs) { 200 | // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does 201 | // not contain a reference to the drag that just exited. single-threaded js to the 202 | // rescue: we'll just keep it for ourselves. 203 | if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; 204 | return _lastDragObject; 205 | }, 206 | 207 | getDragScope : function(el) { 208 | var id = jsPlumb.getId(el), 209 | dd = _draggablesById[id]; 210 | return dd.scope; 211 | }, 212 | 213 | getDropEvent : function(args) { 214 | return args[0]; 215 | }, 216 | 217 | getDropScope : function(el) { 218 | var id = jsPlumb.getId(el); 219 | return _droppableScopesById[id]; 220 | }, 221 | 222 | getDOMElement : function(el) { 223 | if (typeof(el) == "String") 224 | return document.getElementById(el); 225 | else if (el._node) 226 | return el._node; 227 | else return el; 228 | }, 229 | 230 | getElementObject : _getElementObject, 231 | 232 | getOffset : function(el) { 233 | var o = Y.DOM.getXY(el._node); 234 | return {left:o[0], top:o[1]}; 235 | }, 236 | 237 | getOriginalEvent : function(e) { 238 | return e._event; 239 | }, 240 | 241 | getPageXY : function(eventObject) { 242 | return [eventObject.pageX, eventObject.pageY]; 243 | }, 244 | 245 | getParent : function(el) { 246 | return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); 247 | }, 248 | 249 | getScrollLeft : function(el) { 250 | return 0; 251 | }, 252 | 253 | getScrollTop : function(el) { 254 | return 0; 255 | }, 256 | 257 | getSelector : function(spec) { 258 | var s = Y.all(spec); 259 | return s && s ._nodes ? s._nodes : []; 260 | }, 261 | 262 | getSize : function(el) { 263 | return [ el._node.offsetWidth, el._node.offsetHeight ]; 264 | }, 265 | 266 | getTagName : function(el) { 267 | var e = jsPlumb.CurrentLibrary.getElementObject(el); 268 | return e != null && e._node != null ? e._node.tagName : null; 269 | }, 270 | 271 | getUIPosition : function(args) { 272 | var n = args[0].currentTarget.el._node, 273 | o = Y.DOM.getXY(n); 274 | return {left:o[0], top:o[1]}; 275 | }, 276 | 277 | hasClass : function(el, clazz) { 278 | return el.hasClass(clazz); 279 | }, 280 | 281 | initDraggable : function(el, options, isPlumbedComponent) { 282 | var _opts = _getDDOptions(options), 283 | id = jsPlumb.getId(el); 284 | _opts.node = "#" + id; 285 | var dd = new Y.DD.Drag(_opts); 286 | dd.el = el; 287 | 288 | if (isPlumbedComponent) { 289 | var scope = options['scope'] || jsPlumb.Defaults.Scope; 290 | dd.scope = scope; 291 | _add(_draggablesByScope, scope, dd); 292 | } 293 | 294 | _draggablesById[id] = dd; 295 | 296 | _attachListeners(dd, options, ddEvents); 297 | }, 298 | 299 | initDroppable : function(el, options) { 300 | var _opts = _getDDOptions(options), 301 | id = jsPlumb.getId(el); 302 | _opts.node = "#" + id; 303 | var dd = new Y.DD.Drop(_opts); 304 | 305 | _droppableOptions[id] = options; 306 | 307 | options = _extend({}, options); 308 | var scope = options['scope'] || jsPlumb.Defaults.Scope; 309 | _droppableScopesById[id] = scope; 310 | 311 | options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { 312 | if (e.drag.scope !== scope) return true; 313 | _checkHover(el, true); 314 | }, true); 315 | options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { 316 | _checkHover(el, false); 317 | }); 318 | options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { 319 | if (e.drag.scope !== scope) return true; 320 | _checkHover(el, false); 321 | }, true); 322 | 323 | _attachListeners(dd, options, ddEvents); 324 | }, 325 | 326 | isAlreadyDraggable : function(el) { 327 | el = _getElementObject(el); 328 | return el.hasClass("yui3-dd-draggable"); 329 | }, 330 | 331 | isDragSupported : function(el) { return true; }, 332 | isDropSupported : function(el) { return true; }, 333 | removeClass : function(el, clazz) { 334 | jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); 335 | }, 336 | removeElement : function(el) { _getElementObject(el).remove(); }, 337 | 338 | setAttribute : function(el, attributeName, attributeValue) { 339 | el.setAttribute(attributeName, attributeValue); 340 | }, 341 | 342 | /** 343 | * sets the draggable state for the given element 344 | */ 345 | setDraggable : function(el, draggable) { 346 | var id = jsPlumb.getId(el), 347 | dd = _draggablesById[id]; 348 | if (dd) dd.set("lock", !draggable); 349 | }, 350 | 351 | setDragScope : function(el, scope) { 352 | var id = jsPlumb.getId(el), 353 | dd = _draggablesById[id]; 354 | if (dd) dd.scope = scope; 355 | }, 356 | 357 | setOffset : function(el, o) { 358 | el = _getElementObject(el); 359 | el.set("top", o.top); 360 | el.set("left", o.left); 361 | }, 362 | 363 | stopDrag : function() { 364 | Y.DD.DDM.stopDrag(); 365 | }, 366 | 367 | trigger : function(el, event, originalEvent) { 368 | originalEvent.stopPropagation(); 369 | _getElementObject(el).simulate(event, { 370 | pageX:originalEvent.pageX, 371 | pageY:originalEvent.pageY, 372 | clientX:originalEvent.clientX, 373 | clientY:originalEvent.clientY 374 | }); 375 | }, 376 | 377 | /** 378 | * event unbinding wrapper. 379 | */ 380 | unbind : function(el, event, callback) { 381 | _getElementObject(el).detach(event, callback); 382 | } 383 | }; 384 | })(); -------------------------------------------------------------------------------- /admin/js/jsplumb/jquery.jsPlumb-1.3.13-RC1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jsPlumb 3 | * 4 | * Title:jsPlumb 1.3.13 5 | * 6 | * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas 7 | * elements, or VML. 8 | * 9 | * This file contains the jQuery adapter. 10 | * 11 | * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) 12 | * 13 | * http://jsplumb.org 14 | * http://github.com/sporritt/jsplumb 15 | * http://code.google.com/p/jsplumb 16 | * 17 | * Dual licensed under the MIT and GPL2 licenses. 18 | */ 19 | /* 20 | * the library specific functions, such as find offset, get id, get attribute, extend etc. 21 | * the full list is: 22 | * 23 | * addClass adds a class to the given element 24 | * animate calls the underlying library's animate functionality 25 | * appendElement appends a child element to a parent element. 26 | * bind binds some event to an element 27 | * dragEvents a dictionary of event names 28 | * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. 29 | * getAttribute gets some attribute from an element 30 | * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback 31 | * getDragScope gets the drag scope for a given element. 32 | * getDropScope gets the drop scope for a given element. 33 | * getElementObject turns an id or dom element into an element object of the underlying library's type. 34 | * getOffset gets an element's offset 35 | * getOriginalEvent gets the original browser event from some wrapper event 36 | * getPageXY gets the page event's xy location. 37 | * getParent gets the parent of some element. 38 | * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? 39 | * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? 40 | * getSize gets an element's size. 41 | * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. 42 | * hasClass returns whether or not the given element has the given class. 43 | * initDraggable initializes an element to be draggable 44 | * initDroppable initializes an element to be droppable 45 | * isDragSupported returns whether or not drag is supported for some element. 46 | * isDropSupported returns whether or not drop is supported for some element. 47 | * removeClass removes a class from a given element. 48 | * removeElement removes some element completely from the DOM. 49 | * setAttribute sets an attribute on some element. 50 | * setDraggable sets whether or not some element should be draggable. 51 | * setDragScope sets the drag scope for a given element. 52 | * setOffset sets the offset of some element. 53 | * trigger triggers some event on an element. 54 | * unbind unbinds some listener from some element. 55 | */ 56 | (function($) { 57 | 58 | //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; 59 | 60 | jsPlumb.CurrentLibrary = { 61 | 62 | /** 63 | * adds the given class to the element object. 64 | */ 65 | addClass : function(el, clazz) { 66 | el = jsPlumb.CurrentLibrary.getElementObject(el); 67 | try { 68 | if (el[0].className.constructor == SVGAnimatedString) { 69 | jsPlumb.util.svg.addClass(el[0], clazz); 70 | } 71 | } 72 | catch (e) { 73 | // SVGAnimatedString not supported; no problem. 74 | } 75 | el.addClass(clazz); 76 | }, 77 | 78 | /** 79 | * animates the given element. 80 | */ 81 | animate : function(el, properties, options) { 82 | el.animate(properties, options); 83 | }, 84 | 85 | /** 86 | * appends the given child to the given parent. 87 | */ 88 | appendElement : function(child, parent) { 89 | jsPlumb.CurrentLibrary.getElementObject(parent).append(child); 90 | }, 91 | 92 | /** 93 | * executes an ajax call. 94 | */ 95 | ajax : function(params) { 96 | params = params || {}; 97 | params.type = params.type || "get"; 98 | $.ajax(params); 99 | }, 100 | 101 | /** 102 | * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, 103 | * uses 'on'. 104 | */ 105 | bind : function(el, event, callback) { 106 | el = jsPlumb.CurrentLibrary.getElementObject(el); 107 | el.bind(event, callback); 108 | }, 109 | 110 | /** 111 | * mapping of drag events for jQuery 112 | */ 113 | dragEvents : { 114 | 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', 115 | 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' 116 | }, 117 | 118 | /** 119 | * wrapper around the library's 'extend' functionality (which it hopefully has. 120 | * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you 121 | * instead. it's not like its hard. 122 | */ 123 | extend : function(o1, o2) { 124 | return $.extend(o1, o2); 125 | }, 126 | 127 | /** 128 | * gets the named attribute from the given element object. 129 | */ 130 | getAttribute : function(el, attName) { 131 | return el.attr(attName); 132 | }, 133 | 134 | getClientXY : function(eventObject) { 135 | return [eventObject.clientX, eventObject.clientY]; 136 | }, 137 | 138 | /** 139 | * takes the args passed to an event function and returns you an object representing that which is being dragged. 140 | */ 141 | getDragObject : function(eventArgs) { 142 | return eventArgs[1].draggable; 143 | }, 144 | 145 | getDragScope : function(el) { 146 | return el.draggable("option", "scope"); 147 | }, 148 | 149 | getDropEvent : function(args) { 150 | return args[0]; 151 | }, 152 | 153 | getDropScope : function(el) { 154 | return el.droppable("option", "scope"); 155 | }, 156 | 157 | /** 158 | * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), 159 | * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other 160 | * two cases). this is the opposite of getElementObject below. 161 | */ 162 | getDOMElement : function(el) { 163 | if (typeof(el) == "string") return document.getElementById(el); 164 | else if (el.context || el.length != null) return el[0]; 165 | else return el; 166 | }, 167 | 168 | /** 169 | * gets an "element object" from the given input. this means an object that is used by the 170 | * underlying library on which jsPlumb is running. 'el' may already be one of these objects, 171 | * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup 172 | * function is used to find the element, using the given String as the element's id. 173 | * 174 | */ 175 | getElementObject : function(el) { 176 | return typeof(el) == "string" ? $("#" + el) : $(el); 177 | }, 178 | 179 | /** 180 | * gets the offset for the element object. this should return a js object like this: 181 | * 182 | * { left:xxx, top: xxx } 183 | */ 184 | getOffset : function(el) { 185 | return el.offset(); 186 | }, 187 | 188 | getOriginalEvent : function(e) { 189 | return e.originalEvent; 190 | }, 191 | 192 | getPageXY : function(eventObject) { 193 | return [eventObject.pageX, eventObject.pageY]; 194 | }, 195 | 196 | getParent : function(el) { 197 | return jsPlumb.CurrentLibrary.getElementObject(el).parent(); 198 | }, 199 | 200 | getScrollLeft : function(el) { 201 | return el.scrollLeft(); 202 | }, 203 | 204 | getScrollTop : function(el) { 205 | return el.scrollTop(); 206 | }, 207 | 208 | getSelector : function(spec) { 209 | return $(spec); 210 | }, 211 | 212 | /** 213 | * gets the size for the element object, in an array : [ width, height ]. 214 | */ 215 | getSize : function(el) { 216 | return [el.outerWidth(), el.outerHeight()]; 217 | }, 218 | 219 | getTagName : function(el) { 220 | var e = jsPlumb.CurrentLibrary.getElementObject(el); 221 | return e.length > 0 ? e[0].tagName : null; 222 | }, 223 | 224 | /** 225 | * takes the args passed to an event function and returns you an object that gives the 226 | * position of the object being moved, as a js object with the same params as the result of 227 | * getOffset, ie: { left: xxx, top: xxx }. 228 | * 229 | * different libraries have different signatures for their event callbacks. 230 | * see getDragObject as well 231 | */ 232 | getUIPosition : function(eventArgs) { 233 | 234 | // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes 235 | // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect 236 | // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which 237 | // i don't like. 238 | 239 | /*if ( getBoundingClientRectSupported ) { 240 | var r = eventArgs[1].helper[0].getBoundingClientRect(); 241 | return { left : r.left, top: r.top }; 242 | } else {*/ 243 | if (eventArgs.length == 1) { 244 | ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; 245 | } 246 | else { 247 | var ui = eventArgs[1], _offset = ui.offset; 248 | ret = _offset || ui.absolutePosition; 249 | } 250 | return ret; 251 | }, 252 | 253 | hasClass : function(el, clazz) { 254 | return el.hasClass(clazz); 255 | }, 256 | 257 | /** 258 | * initialises the given element to be draggable. 259 | */ 260 | initDraggable : function(el, options, isPlumbedComponent) { 261 | options = options || {}; 262 | // remove helper directive if present. 263 | options.helper = null; 264 | if (isPlumbedComponent) 265 | options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; 266 | el.draggable(options); 267 | }, 268 | 269 | /** 270 | * initialises the given element to be droppable. 271 | */ 272 | initDroppable : function(el, options) { 273 | options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; 274 | el.droppable(options); 275 | }, 276 | 277 | isAlreadyDraggable : function(el) { 278 | el = jsPlumb.CurrentLibrary.getElementObject(el); 279 | return el.hasClass("ui-draggable"); 280 | }, 281 | 282 | /** 283 | * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. 284 | */ 285 | isDragSupported : function(el, options) { 286 | return el.draggable; 287 | }, 288 | 289 | /** 290 | * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. 291 | */ 292 | isDropSupported : function(el, options) { 293 | return el.droppable; 294 | }, 295 | 296 | /** 297 | * removes the given class from the element object. 298 | */ 299 | removeClass : function(el, clazz) { 300 | el = jsPlumb.CurrentLibrary.getElementObject(el); 301 | try { 302 | if (el[0].className.constructor == SVGAnimatedString) { 303 | jsPlumb.util.svg.removeClass(el[0], clazz); 304 | } 305 | } 306 | catch (e) { 307 | // SVGAnimatedString not supported; no problem. 308 | } 309 | el.removeClass(clazz); 310 | }, 311 | 312 | removeElement : function(element, parent) { 313 | jsPlumb.CurrentLibrary.getElementObject(element).remove(); 314 | }, 315 | 316 | /** 317 | * sets the named attribute on the given element object. 318 | */ 319 | setAttribute : function(el, attName, attValue) { 320 | el.attr(attName, attValue); 321 | }, 322 | 323 | /** 324 | * sets the draggable state for the given element 325 | */ 326 | setDraggable : function(el, draggable) { 327 | el.draggable("option", "disabled", !draggable); 328 | }, 329 | 330 | /** 331 | * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) 332 | * @param el 333 | * @param scope 334 | */ 335 | setDragScope : function(el, scope) { 336 | el.draggable("option", "scope", scope); 337 | }, 338 | 339 | setOffset : function(el, o) { 340 | jsPlumb.CurrentLibrary.getElementObject(el).offset(o); 341 | }, 342 | 343 | /** 344 | * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. 345 | * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff 346 | * from the originalEvent to put in an options object for YUI. 347 | * @param el 348 | * @param event 349 | * @param originalEvent 350 | */ 351 | trigger : function(el, event, originalEvent) { 352 | //originalEvent.stopPropagation(); 353 | //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); 354 | var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); 355 | h(originalEvent); 356 | //originalEvent.stopPropagation(); 357 | }, 358 | 359 | /** 360 | * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, 361 | * uses..something else. 362 | */ 363 | unbind : function(el, event, callback) { 364 | el = jsPlumb.CurrentLibrary.getElementObject(el); 365 | el.unbind(event, callback); 366 | } 367 | }; 368 | 369 | $(document).ready(jsPlumb.init); 370 | 371 | })(jQuery); 372 | 373 | -------------------------------------------------------------------------------- /admin/js/require-2.0.4.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.0.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(Y){function x(b){return J.call(b)==="[object Function]"}function G(b){return J.call(b)==="[object Array]"}function q(b,c){if(b){var e;for(e=0;e-1;e-=1)if(b[e]&&c(b[e],e,b))break}}function y(b,c){for(var e in b)if(b.hasOwnProperty(e)&&c(b[e],e))break}function K(b,c,e,i){c&&y(c,function(c,j){if(e||!b.hasOwnProperty(j))i&&typeof c!=="string"?(b[j]||(b[j]={}),K(b[j],c,e,i)):b[j]=c});return b}function s(b, 8 | c){return function(){return c.apply(b,arguments)}}function Z(b){if(!b)return b;var c=Y;q(b.split("."),function(b){c=c[b]});return c}function $(b,c,e){return function(){var i=fa.call(arguments,0),g;if(e&&x(g=i[i.length-1]))g.__requireJsBuild=!0;i.push(c);return b.apply(null,i)}}function aa(b,c,e){q([["toUrl"],["undef"],["defined","requireDefined"],["specified","requireSpecified"]],function(i){var g=i[1]||i[0];b[i[0]]=c?$(c[g],e):function(){var b=z[O];return b[g].apply(b,arguments)}})}function H(b, 9 | c,e,i){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=i;if(e)c.originalError=e;return c}function ga(){if(I&&I.readyState==="interactive")return I;N(document.getElementsByTagName("script"),function(b){if(b.readyState==="interactive")return I=b});return I}var ha=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ia=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,ba=/\.js$/,ja=/^\.\//,J=Object.prototype.toString,A=Array.prototype,fa=A.slice,ka=A.splice,w=!!(typeof window!== 10 | "undefined"&&navigator&&document),ca=!w&&typeof importScripts!=="undefined",la=w&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,O="_",S=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",z={},p={},P=[],L=!1,j,t,C,u,D,I,E,da,ea;if(typeof define==="undefined"){if(typeof requirejs!=="undefined"){if(x(requirejs))return;p=requirejs;requirejs=void 0}typeof require!=="undefined"&&!x(require)&&(p=require,require=void 0);j=requirejs=function(b,c,e,i){var g=O,r;!G(b)&& 11 | typeof b!=="string"&&(r=b,G(c)?(b=c,c=e,e=i):b=[]);if(r&&r.context)g=r.context;(i=z[g])||(i=z[g]=j.s.newContext(g));r&&i.configure(r);return i.require(b,c,e)};j.config=function(b){return j(b)};require||(require=j);j.version="2.0.4";j.jsExtRegExp=/^\/|:|\?|\.js$/;j.isBrowser=w;A=j.s={contexts:z,newContext:function(b){function c(a,d,o){var l=d&&d.split("/"),f=l,b=k.map,c=b&&b["*"],e,g,h;if(a&&a.charAt(0)===".")if(d){f=k.pkgs[d]?l=[d]:l.slice(0,l.length-1);d=a=f.concat(a.split("/"));for(f=0;d[f];f+= 12 | 1)if(e=d[f],e===".")d.splice(f,1),f-=1;else if(e==="..")if(f===1&&(d[2]===".."||d[0]===".."))break;else f>0&&(d.splice(f-1,2),f-=2);f=k.pkgs[d=a[0]];a=a.join("/");f&&a===d+"/"+f.main&&(a=d)}else a.indexOf("./")===0&&(a=a.substring(2));if(o&&(l||c)&&b){d=a.split("/");for(f=d.length;f>0;f-=1){g=d.slice(0,f).join("/");if(l)for(e=l.length;e>0;e-=1)if(o=b[l.slice(0,e).join("/")])if(o=o[g]){h=o;break}!h&&c&&c[g]&&(h=c[g]);if(h){d.splice(0,f,h);a=d.join("/");break}}}return a}function e(a){w&&q(document.getElementsByTagName("script"), 13 | function(d){if(d.getAttribute("data-requiremodule")===a&&d.getAttribute("data-requirecontext")===h.contextName)return d.parentNode.removeChild(d),!0})}function i(a){var d=k.paths[a];if(d&&G(d)&&d.length>1)return e(a),d.shift(),h.undef(a),h.require([a]),!0}function g(a,d,o,b){var f=a?a.indexOf("!"):-1,v=null,e=d?d.name:null,g=a,i=!0,j="",k,m;a||(i=!1,a="_@r"+(N+=1));f!==-1&&(v=a.substring(0,f),a=a.substring(f+1,a.length));v&&(v=c(v,e,b),m=n[v]);a&&(v?j=m&&m.normalize?m.normalize(a,function(a){return c(a, 14 | e,b)}):c(a,e,b):(j=c(a,e,b),k=h.nameToUrl(j)));a=v&&!m&&!o?"_unnormalized"+(O+=1):"";return{prefix:v,name:j,parentMap:d,unnormalized:!!a,url:k,originalName:g,isDefine:i,id:(v?v+"!"+j:j)+a}}function r(a){var d=a.id,o=m[d];o||(o=m[d]=new h.Module(a));return o}function p(a,d,o){var b=a.id,f=m[b];if(n.hasOwnProperty(b)&&(!f||f.defineEmitComplete))d==="defined"&&o(n[b]);else r(a).on(d,o)}function B(a,d){var b=a.requireModules,l=!1;if(d)d(a);else if(q(b,function(d){if(d=m[d])d.error=a,d.events.error&&(l= 15 | !0,d.emit("error",a))}),!l)j.onError(a)}function u(){P.length&&(ka.apply(F,[F.length-1,0].concat(P)),P=[])}function t(a,d,b){a=a&&a.map;d=$(b||h.require,a,d);aa(d,h,a);d.isBrowser=w;return d}function z(a){delete m[a];q(M,function(d,b){if(d.map.id===a)return M.splice(b,1),d.defined||(h.waitCount-=1),!0})}function A(a,d){var b=a.map.id,l=a.depMaps,f;if(a.inited){if(d[b])return a;d[b]=!0;q(l,function(a){if(a=m[a.id])return!a.inited||!a.enabled?(f=null,delete d[b],!0):f=A(a,K({},d))});return f}}function C(a, 16 | d,b){var l=a.map.id,f=a.depMaps;if(a.inited&&a.map.isDefine){if(d[l])return n[l];d[l]=a;q(f,function(f){var f=f.id,c=m[f];!Q[f]&&c&&(!c.inited||!c.enabled?b[l]=!0:(c=C(c,d,b),b[f]||a.defineDepById(f,c)))});a.check(!0);return n[l]}}function D(a){a.check()}function E(){var a=k.waitSeconds*1E3,d=a&&h.startTime+a<(new Date).getTime(),b=[],l=!1,f=!0,c,g,j;if(!T){T=!0;y(m,function(a){c=a.map;g=c.id;if(a.enabled&&!a.error)if(!a.inited&&d)i(g)?l=j=!0:(b.push(g),e(g));else if(!a.inited&&a.fetched&&c.isDefine&& 17 | (l=!0,!c.prefix))return f=!1});if(d&&b.length)return a=H("timeout","Load timeout for modules: "+b,null,b),a.contextName=h.contextName,B(a);f&&(q(M,function(a){if(!a.defined){var a=A(a,{}),d={};a&&(C(a,d,{}),y(d,D))}}),y(m,D));if((!d||j)&&l)if((w||ca)&&!U)U=setTimeout(function(){U=0;E()},50);T=!1}}function V(a){r(g(a[0],null,!0)).init(a[1],a[2])}function J(a){var a=a.currentTarget||a.srcElement,d=h.onScriptLoad;a.detachEvent&&!S?a.detachEvent("onreadystatechange",d):a.removeEventListener("load",d, 18 | !1);d=h.onScriptError;a.detachEvent&&!S||a.removeEventListener("error",d,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}var k={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{}},m={},W={},F=[],n={},R={},N=1,O=1,M=[],T,X,h,Q,U;Q={require:function(a){return t(a)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports=n[a.map.id]={}},module:function(a){return a.module={id:a.map.id,uri:a.map.url,config:function(){return k.config&&k.config[a.map.id]||{}},exports:n[a.map.id]}}}; 19 | X=function(a){this.events=W[a.id]||{};this.map=a;this.shim=k.shim[a.id];this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};X.prototype={init:function(a,d,b,l){l=l||{};if(!this.inited){this.factory=d;if(b)this.on("error",b);else this.events.error&&(b=s(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.depMaps.rjsSkipMap=a.rjsSkipMap;this.errback=b;this.inited=!0;this.ignore=l.ignore;l.enabled||this.enabled?this.enable():this.check()}},defineDepById:function(a, 20 | d){var b;q(this.depMaps,function(d,f){if(d.id===a)return b=f,!0});return this.defineDep(b,d)},defineDep:function(a,d){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=d)},fetch:function(){if(!this.fetched){this.fetched=!0;h.startTime=(new Date).getTime();var a=this.map;if(this.shim)t(this,!0)(this.shim.deps||[],s(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;R[a]|| 21 | (R[a]=!0,h.load(this.map.id,a))},check:function(a){if(this.enabled&&!this.enabling){var d=this.map.id,b=this.depExports,c=this.exports,f=this.factory,e;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(this.depCount<1&&!this.defined){if(x(f)){if(this.events.error)try{c=h.execCb(d,f,b,c)}catch(g){e=g}else c=h.execCb(d,f,b,c);if(this.map.isDefine)if((b=this.module)&&b.exports!==void 0&&b.exports!==this.exports)c=b.exports;else if(c===void 0&&this.usingExports)c= 22 | this.exports;if(e)return e.requireMap=this.map,e.requireModules=[this.map.id],e.requireType="define",B(this.error=e)}else c=f;this.exports=c;if(this.map.isDefine&&!this.ignore&&(n[d]=c,j.onResourceLoad))j.onResourceLoad(h,this.map,this.depMaps);delete m[d];this.defined=!0;h.waitCount-=1;h.waitCount===0&&(M=[])}this.defining=!1;if(!a&&this.defined&&!this.defineEmitted)this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0}}else this.fetch()}},callPlugin:function(){var a= 23 | this.map,d=a.id,b=g(a.prefix,null,!1,!0);p(b,"defined",s(this,function(b){var f=this.map.name,e=this.map.parentMap?this.map.parentMap.name:null;if(this.map.unnormalized){if(b.normalize&&(f=b.normalize(f,function(a){return c(a,e,!0)})||""),b=g(a.prefix+"!"+f,this.map.parentMap,!1,!0),p(b,"defined",s(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),b=m[b.id]){if(this.events.error)b.on("error",s(this,function(a){this.emit("error",a)}));b.enable()}}else f=s(this,function(a){this.init([], 24 | function(){return a},null,{enabled:!0})}),f.error=s(this,function(a){this.inited=!0;this.error=a;a.requireModules=[d];y(m,function(a){a.map.id.indexOf(d+"_unnormalized")===0&&z(a.map.id)});B(a)}),f.fromText=function(a,d){var b=L;b&&(L=!1);r(g(a));j.exec(d);b&&(L=!0);h.completeLoad(a)},b.load(a.name,t(a.parentMap,!0,function(a,d){a.rjsSkipMap=!0;return h.require(a,d)}),f,k)}));h.enable(b,this);this.pluginMaps[b.id]=b},enable:function(){this.enabled=!0;if(!this.waitPushed)M.push(this),h.waitCount+= 25 | 1,this.waitPushed=!0;this.enabling=!0;q(this.depMaps,s(this,function(a,d){var b,c;if(typeof a==="string"){a=g(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.depMaps.rjsSkipMap);this.depMaps[d]=a;if(b=Q[a.id]){this.depExports[d]=b(this);return}this.depCount+=1;p(a,"defined",s(this,function(a){this.defineDep(d,a);this.check()}));this.errback&&p(a,"error",this.errback)}b=a.id;c=m[b];!Q[b]&&c&&!c.enabled&&h.enable(a,this)}));y(this.pluginMaps,s(this,function(a){var b=m[a.id];b&&!b.enabled&& 26 | h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){q(this.events[a],function(a){a(b)});a==="error"&&delete this.events[a]}};return h={config:k,contextName:b,registry:m,defined:n,urlFetched:R,waitCount:0,defQueue:F,Module:X,makeModuleMap:g,configure:function(a){a.baseUrl&&a.baseUrl.charAt(a.baseUrl.length-1)!=="/"&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e=k.paths,f=k.map;K(k,a,!0);k.paths=K(e,a.paths,!0);if(a.map)k.map= 27 | K(f||{},a.map,!0,!0);if(a.shim)y(a.shim,function(a,b){G(a)&&(a={deps:a});if(a.exports&&!a.exports.__buildReady)a.exports=h.makeShimExports(a.exports);c[b]=a}),k.shim=c;if(a.packages)q(a.packages,function(a){a=typeof a==="string"?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ba,"")}}),k.pkgs=b;y(m,function(a,b){a.map=g(b)});if(a.deps||a.callback)h.require(a.deps||[],a.callback)},makeShimExports:function(a){var b;return typeof a==="string"? 28 | (b=function(){return Z(a)},b.exports=a,b):function(){return a.apply(Y,arguments)}},requireDefined:function(a,b){var c=g(a,b,!1,!0).id;return n.hasOwnProperty(c)},requireSpecified:function(a,b){a=g(a,b,!1,!0).id;return n.hasOwnProperty(a)||m.hasOwnProperty(a)},require:function(a,d,c,e){var f;if(typeof a==="string"){if(x(d))return B(H("requireargs","Invalid require call"),c);if(j.get)return j.get(h,a,d);a=g(a,d,!1,!0);a=a.id;return!n.hasOwnProperty(a)?B(H("notloaded",'Module name "'+a+'" has not been loaded yet for context: '+ 29 | b)):n[a]}c&&!x(c)&&(e=c,c=void 0);d&&!x(d)&&(e=d,d=void 0);for(u();F.length;)if(f=F.shift(),f[0]===null)return B(H("mismatch","Mismatched anonymous define() module: "+f[f.length-1]));else V(f);r(g(null,e)).init(a,d,c,{enabled:!0});E();return h.require},undef:function(a){var b=g(a,null,!0),c=m[a];delete n[a];delete R[b.url];delete W[a];if(c){if(c.events.defined)W[a]=c.events;z(a)}},enable:function(a){m[a.id]&&r(a).enable()},completeLoad:function(a){var b=k.shim[a]||{},c=b.exports&&b.exports.exports, 30 | e,f;for(u();F.length;){f=F.shift();if(f[0]===null){f[0]=a;if(e)break;e=!0}else f[0]===a&&(e=!0);V(f)}f=m[a];if(!e&&!n[a]&&f&&!f.inited)if(k.enforceDefine&&(!c||!Z(c)))if(i(a))return;else return B(H("nodefine","No define call for "+a,null,[a]));else V([a,b.deps||[],b.exports]);E()},toUrl:function(a,b){var e=a.lastIndexOf("."),g=null;e!==-1&&(g=a.substring(e,a.length),a=a.substring(0,e));return h.nameToUrl(c(a,b&&b.id,!0),g)},nameToUrl:function(a,b){var c,e,f,g,h,i;if(j.jsExtRegExp.test(a))g=a+(b|| 31 | "");else{c=k.paths;e=k.pkgs;g=a.split("/");for(h=g.length;h>0;h-=1)if(i=g.slice(0,h).join("/"),f=e[i],i=c[i]){G(i)&&(i=i[0]);g.splice(0,h,i);break}else if(f){c=a===f.name?f.location+"/"+f.main:f.location;g.splice(0,h,c);break}g=g.join("/")+(b||".js");g=(g.charAt(0)==="/"||g.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+g}return k.urlArgs?g+((g.indexOf("?")===-1?"?":"&")+k.urlArgs):g},load:function(a,b){j.load(h,a,b)},execCb:function(a,b,c,e){return b.apply(e,c)},onScriptLoad:function(a){if(a.type==="load"|| 32 | la.test((a.currentTarget||a.srcElement).readyState))I=null,a=J(a),h.completeLoad(a.id)},onScriptError:function(a){var b=J(a);if(!i(b.id))return B(H("scripterror","Script error",a,[b.id]))}}}};j({});aa(j);if(w&&(t=A.head=document.getElementsByTagName("head")[0],C=document.getElementsByTagName("base")[0]))t=A.head=C.parentNode;j.onError=function(b){throw b;};j.load=function(b,c,e){var i=b&&b.config||{},g;if(w)return g=i.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"), 33 | g.type=i.scriptType||"text/javascript",g.charset="utf-8",g.async=!0,g.setAttribute("data-requirecontext",b.contextName),g.setAttribute("data-requiremodule",c),g.attachEvent&&!(g.attachEvent.toString&&g.attachEvent.toString().indexOf("[native code")<0)&&!S?(L=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=e,E=g,C?t.insertBefore(g,C):t.appendChild(g),E=null,g;else ca&&(importScripts(e),b.completeLoad(c))}; 34 | w&&N(document.getElementsByTagName("script"),function(b){if(!t)t=b.parentNode;if(u=b.getAttribute("data-main")){if(!p.baseUrl)D=u.split("/"),da=D.pop(),ea=D.length?D.join("/")+"/":"./",p.baseUrl=ea,u=da;u=u.replace(ba,"");p.deps=p.deps?p.deps.concat(u):[u];return!0}});define=function(b,c,e){var i,g;typeof b!=="string"&&(e=c,c=b,b=null);G(c)||(e=c,c=[]);!c.length&&x(e)&&e.length&&(e.toString().replace(ha,"").replace(ia,function(b,e){c.push(e)}),c=(e.length===1?["require"]:["require","exports","module"]).concat(c)); 35 | if(L&&(i=E||ga()))b||(b=i.getAttribute("data-requiremodule")),g=z[i.getAttribute("data-requirecontext")];(g?g.defQueue:P).push([b,c,e])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(p)}})(this); 36 | -------------------------------------------------------------------------------- /admin/js/jsplumb/mootools.jsPlumb-1.3.13-RC1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jsPlumb 3 | * 4 | * Title:jsPlumb 1.3.13 5 | * 6 | * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas 7 | * elements, or VML. 8 | * 9 | * This file contains the MooTools adapter. 10 | * 11 | * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) 12 | * 13 | * http://jsplumb.org 14 | * http://github.com/sporritt/jsplumb 15 | * http://code.google.com/p/jsplumb 16 | * 17 | * Dual licensed under the MIT and GPL2 licenses. 18 | */ 19 | 20 | ;(function() { 21 | 22 | /* 23 | * overrides the FX class to inject 'step' functionality, which MooTools does not 24 | * offer, and which makes me sad. they don't seem keen to add it, either, despite 25 | * the fact that it could be useful: 26 | * 27 | * https://mootools.lighthouseapp.com/projects/2706/tickets/668 28 | * 29 | */ 30 | var jsPlumbMorph = new Class({ 31 | Extends:Fx.Morph, 32 | onStep : null, 33 | initialize : function(el, options) { 34 | this.parent(el, options); 35 | if (options['onStep']) { 36 | this.onStep = options['onStep']; 37 | } 38 | }, 39 | step : function(now) { 40 | this.parent(now); 41 | if (this.onStep) { 42 | try { this.onStep(); } 43 | catch(e) { } 44 | } 45 | } 46 | }); 47 | 48 | var _droppables = {}, 49 | _droppableOptions = {}, 50 | _draggablesByScope = {}, 51 | _draggablesById = {}, 52 | _droppableScopesById = {}; 53 | /* 54 | * 55 | */ 56 | var _executeDroppableOption = function(el, dr, event, originalEvent) { 57 | if (dr) { 58 | var id = dr.get("id"); 59 | if (id) { 60 | var options = _droppableOptions[id]; 61 | if (options) { 62 | if (options[event]) { 63 | options[event](el, dr, originalEvent); 64 | } 65 | } 66 | } 67 | } 68 | }; 69 | 70 | var _checkHover = function(el, entering) { 71 | if (el) { 72 | var id = el.get("id"); 73 | if (id) { 74 | var options = _droppableOptions[id]; 75 | if (options) { 76 | if (options['hoverClass']) { 77 | if (entering) el.addClass(options['hoverClass']); 78 | else el.removeClass(options['hoverClass']); 79 | } 80 | } 81 | } 82 | } 83 | }; 84 | 85 | /** 86 | * adds the given value to the given list, with the given scope. creates the scoped list 87 | * if necessary. 88 | * used by initDraggable and initDroppable. 89 | */ 90 | var _add = function(list, _scope, value) { 91 | var l = list[_scope]; 92 | if (!l) { 93 | l = []; 94 | list[_scope] = l; 95 | } 96 | l.push(value); 97 | }; 98 | 99 | /* 100 | * gets an "element object" from the given input. this means an object that is used by the 101 | * underlying library on which jsPlumb is running. 'el' may already be one of these objects, 102 | * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup 103 | * function is used to find the element, using the given String as the element's id. 104 | */ 105 | var _getElementObject = function(el) { 106 | return $(el); 107 | }; 108 | 109 | jsPlumb.CurrentLibrary = { 110 | 111 | /** 112 | * adds the given class to the element object. 113 | */ 114 | addClass : function(el, clazz) { 115 | el = jsPlumb.CurrentLibrary.getElementObject(el) 116 | try { 117 | if (el.className.constructor == SVGAnimatedString) { 118 | jsPlumbUtil.svg.addClass(el, clazz); 119 | } 120 | else el.addClass(clazz); 121 | } 122 | catch (e) { 123 | // SVGAnimatedString not supported; no problem. 124 | el.addClass(clazz); 125 | } 126 | }, 127 | 128 | animate : function(el, properties, options) { 129 | var m = new jsPlumbMorph(el, options); 130 | m.start(properties); 131 | }, 132 | 133 | appendElement : function(child, parent) { 134 | _getElementObject(parent).grab(child); 135 | }, 136 | 137 | bind : function(el, event, callback) { 138 | el = _getElementObject(el); 139 | el.addEvent(event, callback); 140 | }, 141 | 142 | dragEvents : { 143 | 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', 144 | 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' 145 | }, 146 | 147 | /* 148 | * wrapper around the library's 'extend' functionality (which it hopefully has. 149 | * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you 150 | * instead. it's not like its hard. 151 | */ 152 | extend : function(o1, o2) { 153 | return $extend(o1, o2); 154 | }, 155 | 156 | /** 157 | * gets the named attribute from the given element object. 158 | */ 159 | getAttribute : function(el, attName) { 160 | return el.get(attName); 161 | }, 162 | 163 | getClientXY : function(eventObject) { 164 | return [eventObject.event.clientX, eventObject.event.clientY]; 165 | }, 166 | 167 | getDragObject : function(eventArgs) { 168 | return eventArgs[0]; 169 | }, 170 | 171 | getDragScope : function(el) { 172 | var id = jsPlumb.getId(el), 173 | drags = _draggablesById[id]; 174 | return drags[0].scope; 175 | }, 176 | 177 | getDropEvent : function(args) { 178 | return args[2]; 179 | }, 180 | 181 | getDropScope : function(el) { 182 | var id = jsPlumb.getId(el); 183 | return _droppableScopesById[id]; 184 | }, 185 | 186 | getDOMElement : function(el) { 187 | // MooTools just decorates the DOM elements. so we have either an ID or an Element here. 188 | return typeof(el) == "String" ? document.getElementById(el) : el; 189 | }, 190 | 191 | getElementObject : _getElementObject, 192 | 193 | /* 194 | gets the offset for the element object. this should return a js object like this: 195 | 196 | { left:xxx, top: xxx} 197 | */ 198 | getOffset : function(el) { 199 | var p = el.getPosition(); 200 | return { left:p.x, top:p.y }; 201 | }, 202 | 203 | getOriginalEvent : function(e) { 204 | return e.event; 205 | }, 206 | 207 | getPageXY : function(eventObject) { 208 | return [eventObject.event.pageX, eventObject.event.pageY]; 209 | }, 210 | 211 | getParent : function(el) { 212 | return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); 213 | }, 214 | 215 | getScrollLeft : function(el) { 216 | return null; 217 | }, 218 | 219 | getScrollTop : function(el) { 220 | return null; 221 | }, 222 | 223 | getSelector : function(spec) { 224 | return $$(spec); 225 | }, 226 | 227 | getSize : function(el) { 228 | var s = el.getSize(); 229 | return [s.x, s.y]; 230 | }, 231 | 232 | getTagName : function(el) { 233 | var e = jsPlumb.CurrentLibrary.getElementObject(el); 234 | return e != null ? e.tagName : null; 235 | }, 236 | 237 | /* 238 | * takes the args passed to an event function and returns you an object that gives the 239 | * position of the object being moved, as a js object with the same params as the result of 240 | * getOffset, ie: { left: xxx, top: xxx }. 241 | */ 242 | getUIPosition : function(eventArgs) { 243 | var ui = eventArgs[0], 244 | p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); 245 | return { left:p.x, top:p.y }; 246 | }, 247 | 248 | hasClass : function(el, clazz) { 249 | return el.hasClass(clazz); 250 | }, 251 | 252 | initDraggable : function(el, options, isPlumbedComponent) { 253 | var id = jsPlumb.getId(el); 254 | var drag = _draggablesById[id]; 255 | if (!drag) { 256 | var originalZIndex = 0, 257 | originalCursor = null, 258 | dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; 259 | 260 | options['onStart'] = jsPlumb.wrap(options['onStart'], function() { 261 | originalZIndex = this.element.getStyle('z-index'); 262 | this.element.setStyle('z-index', dragZIndex); 263 | drag.originalZIndex = originalZIndex; 264 | if (jsPlumb.Defaults.DragOptions.cursor) { 265 | originalCursor = this.element.getStyle('cursor'); 266 | this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); 267 | } 268 | }); 269 | 270 | options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { 271 | this.element.setStyle('z-index', originalZIndex); 272 | if (originalCursor) { 273 | this.element.setStyle('cursor', originalCursor); 274 | } 275 | }); 276 | 277 | // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element 278 | // draggable. this is the only library adapter that has to care about this parameter. 279 | var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), 280 | filterFunc = function(entry) { 281 | return entry.get("id") != el.get("id"); 282 | }, 283 | droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; 284 | 285 | if (isPlumbedComponent) { 286 | 287 | options['droppables'] = droppables; 288 | options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { 289 | if (dr) { 290 | _checkHover(dr, false); 291 | _executeDroppableOption(el, dr, 'onLeave'); 292 | } 293 | }); 294 | options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { 295 | if (dr) { 296 | _checkHover(dr, true); 297 | _executeDroppableOption(el, dr, 'onEnter'); 298 | } 299 | }); 300 | options['onDrop'] = function(el, dr, event) { 301 | if (dr) { 302 | _checkHover(dr, false); 303 | _executeDroppableOption(el, dr, 'onDrop', event); 304 | } 305 | }; 306 | } 307 | else 308 | options["droppables"] = []; 309 | 310 | drag = new Drag.Move(el, options); 311 | drag.scope = scope; 312 | drag.originalZIndex = originalZIndex; 313 | _add(_draggablesById, el.get("id"), drag); 314 | // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) 315 | if (isPlumbedComponent) { 316 | _add(_draggablesByScope, scope, drag); 317 | } 318 | // test for disabled. 319 | if (options.disabled) drag.detach(); 320 | } 321 | return drag; 322 | }, 323 | 324 | initDroppable : function(el, options, isPlumbedComponent, isPermanent) { 325 | var scope = options["scope"]; 326 | _add(_droppables, scope, el); 327 | var id = jsPlumb.getId(el); 328 | 329 | el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. 330 | _droppableOptions[id] = options; 331 | _droppableScopesById[id] = scope; 332 | var filterFunc = function(entry) { return entry.element != el; }, 333 | draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; 334 | for (var i = 0; i < draggables.length; i++) { 335 | draggables[i].droppables.push(el); 336 | } 337 | }, 338 | 339 | isAlreadyDraggable : function(el) { 340 | return _draggablesById[jsPlumb.getId(el)] != null; 341 | }, 342 | 343 | isDragSupported : function(el, options) { 344 | return typeof Drag != 'undefined' ; 345 | }, 346 | 347 | /* 348 | * you need Drag.Move imported to make drop work. 349 | */ 350 | isDropSupported : function(el, options) { 351 | return (typeof Drag != undefined && typeof Drag.Move != undefined); 352 | }, 353 | 354 | /** 355 | * removes the given class from the element object. 356 | */ 357 | removeClass : function(el, clazz) { 358 | el = jsPlumb.CurrentLibrary.getElementObject(el); 359 | try { 360 | if (el.className.constructor == SVGAnimatedString) { 361 | jsPlumbUtil.svg.removeClass(el, clazz); 362 | } 363 | else el.removeClass(clazz); 364 | } 365 | catch (e) { 366 | // SVGAnimatedString not supported; no problem. 367 | el.removeClass(clazz); 368 | } 369 | }, 370 | 371 | removeElement : function(element, parent) { 372 | var el = _getElementObject(element); 373 | if (el) el.dispose(); // ?? 374 | }, 375 | 376 | /** 377 | * sets the named attribute on the given element object. 378 | */ 379 | setAttribute : function(el, attName, attValue) { 380 | el.set(attName, attValue); 381 | }, 382 | 383 | setDraggable : function(el, draggable) { 384 | var draggables = _draggablesById[el.get("id")]; 385 | if (draggables) { 386 | draggables.each(function(d) { 387 | if (draggable) d.attach(); else d.detach(); 388 | }); 389 | } 390 | }, 391 | 392 | setDragScope : function(el, scope) { 393 | var drag = _draggablesById[el.get("id")]; 394 | var filterFunc = function(entry) { 395 | return entry.get("id") != el.get("id"); 396 | }; 397 | var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; 398 | drag[0].droppables = droppables; 399 | }, 400 | 401 | setOffset : function(el, o) { 402 | _getElementObject(el).setPosition({x:o.left, y:o.top}); 403 | }, 404 | 405 | stopDrag : function() { 406 | for (var i in _draggablesById) { 407 | for (var j = 0; j < _draggablesById[i].length; j++) { 408 | var d = _draggablesById[i][j]; 409 | d.stop(); 410 | if (d.originalZIndex != 0) 411 | d.element.setStyle("z-index", d.originalZIndex); 412 | } 413 | } 414 | }, 415 | 416 | /** 417 | * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. 418 | * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff 419 | * from the originalEvent to put in an options object for YUI. 420 | * @param el 421 | * @param event 422 | * @param originalEvent 423 | */ 424 | trigger : function(el, event, originalEvent) { 425 | originalEvent.stopPropagation(); 426 | _getElementObject(el).fireEvent(event, originalEvent); 427 | }, 428 | 429 | unbind : function(el, event, callback) { 430 | el = _getElementObject(el); 431 | el.removeEvent(event, callback); 432 | } 433 | }; 434 | 435 | window.addEvent('domready', jsPlumb.init); 436 | })(); 437 | -------------------------------------------------------------------------------- /http_link.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); 2 | var sys = require("sys"); 3 | var url = require("url"); 4 | var WebSocketClient = require("ws"); 5 | 6 | var port = 9092; 7 | var defaultHost = "localhost"; 8 | var defaultPort = 9000; 9 | var clients = {byID:{},byName:{}}; 10 | var DEFAULT_TIMEOUT = 5*60; // 5 minutes 11 | var DEBUG = true; 12 | var clientID = 0; 13 | var wsClient; 14 | 15 | /* 16 | 17 | Node.js server that accepts pure HTTP requests and pipes the requests through websockets. 18 | The server keeps track of different clients and maintains a websocket connection with spacebrew to pass along future messages. 19 | 20 | HTTP request: 21 | currently only tested as a GET request 22 | 23 | CAN TEST WITH: 24 | http://localhost:9092/?config={%22config%22:{%22name%22:%22test%22,%22publish%22:{%22messages%22:[{%22name%22:%22output%22,%22type%22:%22string%22},{%22name%22:%22out%22,%22type%22:%22string%22}]},%22subscribe%22:{%22messages%22:[{%22name%22:%22input%22,%22type%22:%22string%22,%22bufferSize%22:3}]}}} 25 | http://localhost:9092/?clientID=0&poll=true 26 | http://localhost:9092/?clientID=0&publish=[{%22message%22:{%22clientName%22:%22test%22,%22name%22:%22output%22,%22type%22:%22string%22,%22value%22:%22hello!%22}}] 27 | */ 28 | 29 | 30 | /** 31 | * Returns the number of seconds since the Unix epoch. 32 | * @return {Number} The number of seconds since the Unix epoch 33 | */ 34 | var getSecondsEpoch = function() { 35 | var d = new Date(); 36 | var seconds = d.getTime() / 1000; 37 | return seconds; 38 | }; 39 | 40 | /** 41 | * Sends the provided client's configuration to the SB server. 42 | * This function will also create some data structures for queuing messages for 43 | * the client. 44 | * @param {json} clientData The data storing all info related to this client 45 | * @param {json} output The json object returned in the response 46 | */ 47 | var configureClient = function(clientData, output) { 48 | 49 | if (!clientData.received){ 50 | clientData.received = {}; 51 | } 52 | var subscribers = clientData.config.config.subscribe.messages; 53 | 54 | //first remove any buffers that don't match current client definition 55 | for (var type in clientData.received){ 56 | for(var name in clientData.received[type]){ 57 | var found = false; 58 | for(var index in subscribers){ 59 | if (subscribers[index].type == type && subscribers[index].name == name){ 60 | found = true; 61 | break; 62 | } 63 | } 64 | if (!found){ 65 | delete clientData.received[type][name]; 66 | } 67 | } 68 | if (Object.keys(clientData.received[type]).length === 0){ 69 | delete clientData.received[type]; 70 | } 71 | } 72 | 73 | //the add new buffers to match current client definition 74 | for (var i = subscribers.length - 1; i >= 0; i--) { 75 | var sub = subscribers[i]; 76 | //copying ROOT -> type -> name -> DATA multi-level map from the server 77 | //(see handleMessageMessage in spacebrew.js) 78 | if (!clientData.received[sub.type]){ 79 | clientData.received[sub.type] = {}; 80 | } 81 | if (!clientData.received[sub.type][sub.name]){ 82 | //this will be an array to act as a buffer 83 | //TODO: ensure bufferSize is an integer 84 | clientData.received[sub.type][sub.name] = {bufferSize:sub.bufferSize || 1, buffer:[]}; 85 | if (sub.bufferSize){ 86 | delete sub.bufferSize; 87 | } 88 | } 89 | } 90 | 91 | //send config to SB server 92 | var jsonConfig = JSON.stringify(clientData.config); 93 | if (DEBUG){ 94 | sys.puts("jsonConfig: " + jsonConfig); 95 | } 96 | 97 | //TODO: check if ws is connected 98 | try{ 99 | wsClient.send(jsonConfig); 100 | } 101 | catch (error){ 102 | output.error = true; 103 | output.messages.push("Error while sending config: " + error.name + " msg: " + error.message); 104 | } 105 | }; 106 | 107 | /** 108 | * Handle the json data from the Server and forward it to the appropriate function 109 | * @param {json} json The message sent from the Server 110 | * @return {boolean} True iff the message was a recognized type 111 | */ 112 | var handleMessage = function(json) { 113 | if (json.message){ 114 | var clientData = clients.byName[json.message.clientName]; 115 | if (clientData){ 116 | var nameMap = clientData.received[json.message.type]; 117 | if (nameMap){ 118 | var bufferObj = nameMap[json.message.name]; 119 | if (bufferObj){ 120 | bufferObj.buffer.push(json); 121 | if (bufferObj.buffer.length > bufferObj.bufferSize){ 122 | bufferObj.buffer.splice(0,1); 123 | } 124 | } 125 | } 126 | } 127 | } else if (json.admin){ 128 | } else if (json.config){ 129 | } else if (json.route){ 130 | } else if (json.remove){ 131 | } else { 132 | return false; 133 | } 134 | return true; 135 | }; 136 | 137 | /** 138 | * Called when we receive a message from the Server. 139 | * @param {websocket message} data The websocket message from the Server 140 | */ 141 | var receivedMessage = function(data, flags) { 142 | if (data){ 143 | var json = JSON.parse(data); 144 | //TODO: check if json is an array, otherwise use it as solo message 145 | //when we hit a malformed message, output a warning 146 | if (!handleMessage(json)){ 147 | for(var i = 0, end = json.length; i < end; i++){ 148 | handleMessage(json[i]); 149 | } 150 | } 151 | } 152 | }; 153 | 154 | /** 155 | * Creates the websocket client which is used to communicate with the SB server. 156 | */ 157 | var setupWSClient = function() { 158 | // create the wsclient 159 | wsClient = new WebSocketClient("ws://"+defaultHost+":"+defaultPort); 160 | wsClient.on("open", function(conn){ 161 | //TODO: re-send client configs if this is a re-connect (or generally if there are configs) 162 | console.log("connected"); 163 | }); 164 | wsClient.on("message", receivedMessage); 165 | 166 | wsClient.on("error", function(){ 167 | console.log("ERROR"); 168 | if (DEBUG){ 169 | console.log(arguments); 170 | } 171 | //attempt re-connect after 5 seconds 172 | setTimeout(setupWSClient, 5000); 173 | }); 174 | 175 | wsClient.on("close", function(){ 176 | console.log("CLOSE"); 177 | if (DEBUG){ 178 | console.log(arguments); 179 | } 180 | //attempt re-connect after 5 seconds 181 | setTimeout(setupWSClient, 5000); 182 | }); 183 | }; 184 | 185 | setupWSClient(); 186 | 187 | 188 | /** 189 | * This goes through all registered clients and sees if they have timed out. 190 | * Any timed out clients will be cleaned up and removed from the SB server. 191 | */ 192 | var checkTimeouts = function() { 193 | 194 | for (var id in clients.byID) { 195 | var client = clients.byID[id]; 196 | if( getSecondsEpoch() - client.lastUpdate > client.updateTimeout ) { 197 | //TODO: send 'remove' or 'un-register' command to SB server? 198 | // (would need to be implemented server side as well) 199 | delete clients.byID[id]; 200 | delete clients.byName[client.config.config.name]; 201 | client = undefined; 202 | } 203 | } 204 | }; 205 | 206 | // check timeouts every minute 207 | setInterval(checkTimeouts, 1 * 60 * 1000); 208 | 209 | //the server listens for GET requests passing the data via query string 210 | //the following query string values are accepted/expected 211 | //config= 212 | // This is required the first time a client registers. The 213 | // should be the full stringified JSON that you would normally send via 214 | // websocket to the server. (ie. config={config:{name:.......}}) 215 | // In response to initially registering a client, this server will send back 216 | // a unique identifier used to identify your client in the future. 217 | // You can also send this config value again along with the 218 | // CLIENT_ID (see below) if you need to send a client update. 219 | // **Setting Buffer size** 220 | // Each subscriber defaults to a buffer size of 1, if you want a larger 221 | // buffer, then specify the property 'bufferSize' with an integer value 222 | // within the individual subscriber's definition. 223 | // (ie. messages:[{name:mySub,type:string,bufferSize:5}...]) 224 | //clientID: 225 | // This should be used any time after first registering the client. The 226 | // is the unique identifier returned in the GET response that 227 | // first registers a client with this server. 228 | //poll: 229 | // if true, then the server will provide any stored data for all of the 230 | // subscribers this client has registered. 231 | //publish: 232 | // json stipulating values to send out via this client's registered 233 | // publishers. The format of is an array of publish messages 234 | // where each publish message is formatted as expected by the Spacebrew server 235 | // (ie. [\[,....\]] where is 236 | // {name:,clientName:,type:, 237 | // value::} 238 | //timeout: 239 | // The number of seconds since the last interaction from this client before 240 | // the client is marked as stale. A stale client can be replaced by a new 241 | // client with a matching name. 242 | // The default timeout is 5 minutes. 243 | 244 | http.createServer(function (req, res) { 245 | var config, id, publish, poll, timeout, clientData; 246 | var output = {error:false,messages:[]}; 247 | res.setHeader("Content-Type", "application/json"); 248 | 249 | try { 250 | if(DEBUG) { 251 | sys.puts("request: " + req.url); 252 | } 253 | 254 | if(req.method == "GET") { 255 | 256 | var vals = url.parse(req.url, true).query; 257 | config = vals.config; 258 | if (config){ 259 | try{ 260 | config = JSON.parse(config); 261 | } 262 | catch (error){ 263 | output.error = true; 264 | output.messages.push("Error parsing config: " + error.name + " msg: " + error.message); 265 | config = undefined; 266 | } 267 | } 268 | id = vals.clientID; 269 | poll = vals.poll; 270 | if (poll){ 271 | poll = poll.toLowerCase() == "true"; 272 | } 273 | publish = vals.publish; 274 | if (publish){ 275 | try{ 276 | publish = JSON.parse(publish); 277 | } 278 | catch (error){ 279 | output.error = true; 280 | output.messages.push("Error parsing publish: " + error.name + " msg: " + error.message); 281 | publish = undefined; 282 | } 283 | } 284 | timeout = vals.timeout; 285 | 286 | } else {//"DELETE" "PUT" "POST"... 287 | /* 288 | So There is a big discussion here about which HTTP verbs handle which commands. 289 | The suggestions of REST are that POST is non-idempotent (multiple calls can change server state) 290 | however all other verbs are idempotent. Since most methods change the state of the server, it seems 291 | like we should be using POST almost exclusively, but for eand-users, using GET is so easy... and this isn't a "real" HTTP server... 292 | */ 293 | output.error = true; 294 | output.messages.push("Unsupported request method of " + req.method); 295 | } 296 | 297 | //TODO: method 298 | if (id !== undefined){ 299 | clientData = clients.byID[id]; 300 | clientData.lastUpdate = getSecondsEpoch(); 301 | if (timeout){ 302 | clientData.updateTimeout = timeout; 303 | } 304 | if (!clientData){ 305 | output.error = true; 306 | output.messages.push("ID does not match any registered clients."); 307 | } else if (config){ 308 | //possibly a client config update 309 | if (!config.config){ 310 | output.error = true; 311 | output.messages.push("Invalid config format."); 312 | } else if (config.config.name != clientData.config.config.name){ 313 | output.error = true; 314 | output.messages.push("Supplied client name does not match registered client name."); 315 | } else { 316 | //I believe this is updating both byID and byName 317 | clientData.config = config; 318 | configureClient(clientData, output); 319 | } 320 | } 321 | if (!output.error){ 322 | if (publish){ 323 | try{ 324 | for (var i = 0; i < publish.length; i++) { 325 | wsClient.send(JSON.stringify(publish[i])); 326 | } 327 | } 328 | catch(error){ 329 | output.error = true; 330 | output.messages.push("Error parsing published values: " + error.name + " msg: " + error.message); 331 | } 332 | } 333 | if (poll){ 334 | //return stored values 335 | var messages = []; 336 | for(var type in clientData.received){ 337 | var nameMap = clientData.received[type]; 338 | for (var name in nameMap){ 339 | var bufferObj = nameMap[name]; 340 | if (bufferObj && bufferObj.buffer.length > 0){ 341 | messages = messages.concat(bufferObj.buffer); 342 | bufferObj.buffer = []; 343 | } 344 | } 345 | } 346 | output.received = messages; 347 | } 348 | } 349 | } else if (config){ 350 | if (!config.config){ 351 | output.error = true; 352 | output.messages.push("Config must contain a 'config' object."); 353 | } else { 354 | if (!config.config.name){ 355 | output.error = true; 356 | output.messages.push("Config requires a client 'name'."); 357 | } else { 358 | var existingClient = clients.byName[config.config.name]; 359 | if (existingClient !== undefined){ 360 | if (getSecondsEpoch() - existingClient.lastUpdate > existingClient.updateTimeout){ 361 | delete clients.byName[config.config.name]; 362 | delete clients.byID[existingClient.id]; 363 | existingClient = undefined; 364 | } else { 365 | output.error = true; 366 | output.messages.push("Client with provided name already registered with this http_link."); 367 | } 368 | } 369 | if (existingClient === undefined){ 370 | //valid (perhaps) config 371 | clientData = {"config":config}; 372 | clientData.updateTimeout = timeout || DEFAULT_TIMEOUT; 373 | configureClient(clientData, output); 374 | if (!output.error){ 375 | clients.byID[clientID] = clientData; 376 | clients.byName[config.config.name] = clientData; 377 | id = clientID; 378 | clientID++; 379 | } 380 | } 381 | } 382 | } 383 | } else { 384 | output.error = true; 385 | output.messages.push("Send client config or reference registered client by ID."); 386 | } 387 | } 388 | catch (error){ 389 | output.error = true; 390 | output.messages.push("Unexpected error: " + error.name + " msg: " + error.message); 391 | } 392 | output.clientID = id; 393 | res.end(JSON.stringify(output)); 394 | 395 | }).listen(port); 396 | -------------------------------------------------------------------------------- /admin/js/jsplumb/jsPlumb-renderers-vml-1.3.13-RC1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jsPlumb 3 | * 4 | * Title:jsPlumb 1.3.13 5 | * 6 | * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas 7 | * elements, or VML. 8 | * 9 | * This file contains the VML renderers. 10 | * 11 | * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) 12 | * 13 | * http://jsplumb.org 14 | * http://github.com/sporritt/jsplumb 15 | * http://code.google.com/p/jsplumb 16 | * 17 | * Dual licensed under the MIT and GPL2 licenses. 18 | */ 19 | 20 | ;(function() { 21 | 22 | // http://ajaxian.com/archives/the-vml-changes-in-ie-8 23 | // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ 24 | // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ 25 | 26 | var vmlAttributeMap = { 27 | "stroke-linejoin":"joinstyle", 28 | "joinstyle":"joinstyle", 29 | "endcap":"endcap", 30 | "miterlimit":"miterlimit" 31 | }, 32 | jsPlumbStylesheet = null; 33 | 34 | if (document.createStyleSheet && document.namespaces) { 35 | 36 | var ruleClasses = [ 37 | ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", 38 | "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" 39 | ], 40 | rule = "behavior:url(#default#VML);position:absolute;"; 41 | 42 | jsPlumbStylesheet = document.createStyleSheet(); 43 | 44 | for (var i = 0; i < ruleClasses.length; i++) 45 | jsPlumbStylesheet.addRule(ruleClasses[i], rule); 46 | 47 | // in this page it is also mentioned that IE requires the extra arg to the namespace 48 | // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ 49 | // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. 50 | // var iev = document.documentMode; 51 | //if (!iev || iev < 8) 52 | document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); 53 | //else 54 | // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); 55 | } 56 | 57 | jsPlumb.vml = {}; 58 | 59 | var scale = 1000, 60 | 61 | _groupMap = {}, 62 | _getGroup = function(container, connectorClass) { 63 | var id = jsPlumb.getId(container), 64 | g = _groupMap[id]; 65 | if(!g) { 66 | g = _node("group", [0,0,scale, scale], {"class":connectorClass}); 67 | //g.style.position=absolute; 68 | //g["coordsize"] = "1000,1000"; 69 | g.style.backgroundColor="red"; 70 | _groupMap[id] = g; 71 | jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. 72 | //document.body.appendChild(g); 73 | } 74 | return g; 75 | }, 76 | _atts = function(o, atts) { 77 | for (var i in atts) { 78 | // IE8 fix: setattribute does not work after an element has been added to the dom! 79 | // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ 80 | //o.setAttribute(i, atts[i]); 81 | 82 | /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: 83 | 84 | if (document.documentMode==8) { 85 | ele.opacity=1; 86 | } else { 87 | ele.setAttribute(‘opacity’,1); 88 | } 89 | */ 90 | 91 | o[i] = atts[i]; 92 | } 93 | }, 94 | _node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) { 95 | atts = atts || {}; 96 | var o = document.createElement("jsplumb:" + name); 97 | if (deferToJsPlumbContainer) 98 | _jsPlumb.appendElement(o, parent); 99 | else 100 | jsPlumb.CurrentLibrary.appendElement(o, parent); 101 | o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; 102 | _pos(o, d); 103 | _atts(o, atts); 104 | return o; 105 | }, 106 | _pos = function(o,d, zIndex) { 107 | o.style.left = d[0] + "px"; 108 | o.style.top = d[1] + "px"; 109 | o.style.width= d[2] + "px"; 110 | o.style.height= d[3] + "px"; 111 | o.style.position = "absolute"; 112 | if (zIndex) 113 | o.style.zIndex = zIndex; 114 | }, 115 | _conv = jsPlumb.vml.convertValue = function(v) { 116 | return Math.floor(v * scale); 117 | }, 118 | // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, 119 | // or 1 if not. TODO in the future, support variable opacity. 120 | _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { 121 | if ("transparent" === styleToCheck) 122 | component.setOpacity(type, "0.0"); 123 | else 124 | component.setOpacity(type, "1.0"); 125 | }, 126 | _applyStyles = function(node, style, component, _jsPlumb) { 127 | var styleToWrite = {}; 128 | if (style.strokeStyle) { 129 | styleToWrite["stroked"] = "true"; 130 | var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); 131 | styleToWrite["strokecolor"] = strokeColor; 132 | _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); 133 | styleToWrite["strokeweight"] = style.lineWidth + "px"; 134 | } 135 | else styleToWrite["stroked"] = "false"; 136 | 137 | if (style.fillStyle) { 138 | styleToWrite["filled"] = "true"; 139 | var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); 140 | styleToWrite["fillcolor"] = fillColor; 141 | _maybeSetOpacity(styleToWrite, fillColor, "fill", component); 142 | } 143 | else styleToWrite["filled"] = "false"; 144 | 145 | if(style["dashstyle"]) { 146 | if (component.strokeNode == null) { 147 | component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); 148 | } 149 | else 150 | component.strokeNode.dashstyle = style["dashstyle"]; 151 | } 152 | else if (style["stroke-dasharray"] && style["lineWidth"]) { 153 | var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", 154 | parts = style["stroke-dasharray"].split(sep), 155 | styleToUse = ""; 156 | for(var i = 0; i < parts.length; i++) { 157 | styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); 158 | } 159 | if (component.strokeNode == null) { 160 | component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); 161 | } 162 | else 163 | component.strokeNode.dashstyle = styleToUse; 164 | } 165 | 166 | _atts(node, styleToWrite); 167 | }, 168 | /* 169 | * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. 170 | */ 171 | VmlComponent = function() { 172 | var self = this; 173 | jsPlumb.jsPlumbUIComponent.apply(this, arguments); 174 | this.opacityNodes = { 175 | "stroke":null, 176 | "fill":null 177 | }; 178 | this.initOpacityNodes = function(vml) { 179 | self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); 180 | self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); 181 | }; 182 | this.setOpacity = function(type, value) { 183 | var node = self.opacityNodes[type]; 184 | if (node) node["opacity"] = "" + value; 185 | }; 186 | var displayElements = [ ]; 187 | this.getDisplayElements = function() { 188 | return displayElements; 189 | }; 190 | 191 | this.appendDisplayElement = function(el, doNotAppendToCanvas) { 192 | if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); 193 | displayElements.push(el); 194 | }; 195 | }, 196 | /* 197 | * Base class for Vml connectors. extends VmlComponent. 198 | */ 199 | VmlConnector = jsPlumb.VmlConnector = function(params) { 200 | var self = this; 201 | self.strokeNode = null; 202 | self.canvas = null; 203 | VmlComponent.apply(this, arguments); 204 | var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); 205 | this.paint = function(d, style, anchor) { 206 | if (style != null) { 207 | var path = self.getPath(d), p = { "path":path }; 208 | 209 | //* 210 | if (style.outlineColor) { 211 | var outlineWidth = style.outlineWidth || 1, 212 | outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), 213 | outlineStyle = { 214 | strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), 215 | lineWidth : outlineStrokeWidth 216 | }; 217 | for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; 218 | 219 | if (self.bgCanvas == null) { 220 | p["class"] = clazz; 221 | p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); 222 | self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb, true); 223 | _pos(self.bgCanvas, d, self.getZIndex()); 224 | self.appendDisplayElement(self.bgCanvas, true); 225 | self.attachListeners(self.bgCanvas, self); 226 | self.initOpacityNodes(self.bgCanvas, ["stroke"]); 227 | } 228 | else { 229 | p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); 230 | _pos(self.bgCanvas, d, self.getZIndex()); 231 | _atts(self.bgCanvas, p); 232 | } 233 | 234 | _applyStyles(self.bgCanvas, outlineStyle, self); 235 | } 236 | //*/ 237 | 238 | if (self.canvas == null) { 239 | p["class"] = clazz; 240 | p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); 241 | if (self.tooltip) p["label"] = self.tooltip; 242 | self.canvas = _node("shape", d, p, params.parent, self._jsPlumb, true); 243 | //var group = _getGroup(params.parent); // test of append everything to a group 244 | //group.appendChild(self.canvas); // sort of works but not exactly; 245 | //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups 246 | 247 | self.appendDisplayElement(self.canvas, true); 248 | self.attachListeners(self.canvas, self); 249 | self.initOpacityNodes(self.canvas, ["stroke"]); 250 | } 251 | else { 252 | p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); 253 | _pos(self.canvas, d, self.getZIndex()); 254 | _atts(self.canvas, p); 255 | } 256 | 257 | _applyStyles(self.canvas, style, self, self._jsPlumb); 258 | } 259 | }; 260 | 261 | //self.appendDisplayElement(self.canvas); 262 | 263 | this.reattachListeners = function() { 264 | if (self.canvas) self.reattachListenersForElement(self.canvas, self); 265 | }; 266 | }, 267 | /* 268 | * 269 | * Base class for Vml Endpoints. extends VmlComponent. 270 | * 271 | */ 272 | VmlEndpoint = window.VmlEndpoint = function(params) { 273 | VmlComponent.apply(this, arguments); 274 | var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; 275 | self.canvas = document.createElement("div"); 276 | self.canvas.style["position"] = "absolute"; 277 | 278 | var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); 279 | 280 | //var group = _getGroup(params.parent); 281 | //group.appendChild(self.canvas); 282 | params["_jsPlumb"].appendElement(self.canvas, params.parent); 283 | 284 | if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); 285 | 286 | this.paint = function(d, style, anchor) { 287 | var p = { }; 288 | 289 | jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); 290 | if (vml == null) { 291 | p["class"] = clazz; 292 | vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); 293 | self.attachListeners(vml, self); 294 | 295 | self.appendDisplayElement(vml, true); 296 | self.appendDisplayElement(self.canvas, true); 297 | 298 | self.initOpacityNodes(vml, ["fill"]); 299 | } 300 | else { 301 | _pos(vml, [0,0, d[2], d[3]]); 302 | _atts(vml, p); 303 | } 304 | 305 | _applyStyles(vml, style, self); 306 | }; 307 | 308 | this.reattachListeners = function() { 309 | if (vml) self.reattachListenersForElement(vml, self); 310 | }; 311 | }; 312 | 313 | jsPlumb.Connectors.vml.Bezier = function() { 314 | jsPlumb.Connectors.Bezier.apply(this, arguments); 315 | VmlConnector.apply(this, arguments); 316 | this.getPath = function(d) { 317 | return "m" + _conv(d[4]) + "," + _conv(d[5]) + 318 | " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; 319 | }; 320 | }; 321 | 322 | jsPlumb.Connectors.vml.Straight = function() { 323 | jsPlumb.Connectors.Straight.apply(this, arguments); 324 | VmlConnector.apply(this, arguments); 325 | this.getPath = function(d) { 326 | return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; 327 | }; 328 | }; 329 | 330 | jsPlumb.Connectors.vml.Flowchart = function() { 331 | jsPlumb.Connectors.Flowchart.apply(this, arguments); 332 | VmlConnector.apply(this, arguments); 333 | this.getPath = function(dimensions) { 334 | var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; 335 | // loop through extra points 336 | for (var i = 0; i < dimensions[8]; i++) { 337 | p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); 338 | } 339 | // finally draw a line to the end 340 | p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; 341 | return p; 342 | }; 343 | }; 344 | 345 | jsPlumb.Endpoints.vml.Dot = function() { 346 | jsPlumb.Endpoints.Dot.apply(this, arguments); 347 | VmlEndpoint.apply(this, arguments); 348 | this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; 349 | }; 350 | 351 | jsPlumb.Endpoints.vml.Rectangle = function() { 352 | jsPlumb.Endpoints.Rectangle.apply(this, arguments); 353 | VmlEndpoint.apply(this, arguments); 354 | this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; 355 | }; 356 | 357 | /* 358 | * VML Image Endpoint is the same as the default image endpoint. 359 | */ 360 | jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; 361 | 362 | /** 363 | * placeholder for Blank endpoint in vml renderer. 364 | */ 365 | jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; 366 | 367 | /** 368 | * VML Label renderer. uses the default label renderer (which adds an element to the DOM) 369 | */ 370 | jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; 371 | 372 | /** 373 | * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM) 374 | */ 375 | jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom; 376 | 377 | var AbstractVmlArrowOverlay = function(superclass, originalArgs) { 378 | superclass.apply(this, originalArgs); 379 | VmlComponent.apply(this, originalArgs); 380 | var self = this, path = null; 381 | self.canvas = null; 382 | self.isAppendedAtTopLevel = true; 383 | var getPath = function(d, connectorDimensions) { 384 | return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + 385 | " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + 386 | " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + 387 | " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + 388 | " x e"; 389 | }; 390 | this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { 391 | var p = {}; 392 | if (strokeStyle) { 393 | p["stroked"] = "true"; 394 | p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); 395 | } 396 | if (lineWidth) p["strokeweight"] = lineWidth + "px"; 397 | if (fillStyle) { 398 | p["filled"] = "true"; 399 | p["fillcolor"] = fillStyle; 400 | } 401 | var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), 402 | ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), 403 | xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), 404 | ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), 405 | w = Math.abs(xmax - xmin), 406 | h = Math.abs(ymax - ymin), 407 | dim = [xmin, ymin, w, h]; 408 | 409 | // for VML, we create overlays using shapes that have the same dimensions and 410 | // coordsize as their connector - overlays calculate themselves relative to the 411 | // connector (it's how it's been done since the original canvas implementation, because 412 | // for canvas that makes sense). 413 | p["path"] = getPath(d, connectorDimensions); 414 | p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); 415 | 416 | dim[0] = connectorDimensions[0]; 417 | dim[1] = connectorDimensions[1]; 418 | dim[2] = connectorDimensions[2]; 419 | dim[3] = connectorDimensions[3]; 420 | 421 | if (self.canvas == null) { 422 | //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? 423 | self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb, true); 424 | connector.appendDisplayElement(self.canvas, true); 425 | self.attachListeners(self.canvas, connector); 426 | self.attachListeners(self.canvas, self); 427 | } 428 | else { 429 | _pos(self.canvas, dim); 430 | _atts(self.canvas, p); 431 | } 432 | }; 433 | 434 | this.reattachListeners = function() { 435 | if (self.canvas) self.reattachListenersForElement(self.canvas, self); 436 | }; 437 | 438 | this.cleanup = function() { 439 | if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); 440 | }; 441 | }; 442 | 443 | jsPlumb.Overlays.vml.Arrow = function() { 444 | AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); 445 | }; 446 | 447 | jsPlumb.Overlays.vml.PlainArrow = function() { 448 | AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); 449 | }; 450 | 451 | jsPlumb.Overlays.vml.Diamond = function() { 452 | AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); 453 | }; 454 | })(); -------------------------------------------------------------------------------- /spacebrew_live_persist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Spacebrew Live Persist Module 3 | * ----------------------------- 4 | * 5 | * This module persists all routes added via a standard spacebrew admin. To 6 | * run this module a standalone app you should use the node_persisten_live.js 7 | * script. 8 | * 9 | * Latest Updates: 10 | * - checks if data/routes/live folder already exists, and if not, it creates folder 11 | * - changed error message when script attempts to load a live routes file that 12 | * does not exist 13 | * 14 | * @author: Julio Terra 15 | * @filename: node_server.js 16 | * @date: June 1st, 2013 17 | * @updated with version: 0.3.1 18 | * 19 | */ 20 | 21 | var fs = require("fs") // fs used for file read/write 22 | , WebSocketClient = require('ws') // websocket used for conection to spacebrew 23 | , logger = require('./logger') // logger used to log messages 24 | , livePersister = exports // set livePersister to be a node module 25 | , WS_OPEN = 1 // websockets library open state constant 26 | ; 27 | 28 | /** 29 | * Live Persister module that makes sure that all routes connected via an standard 30 | * Admin remain connected if an app disconnects and then reconnects, or 31 | * if the server fails. 32 | * 33 | * @param {Object} opts Object that holds the following optional settings for 34 | * the livePersister: 35 | * * port: port number of the Spacebrew server 36 | * * host: host of the spacebrew server 37 | * * autosave: saves routes to file 38 | * * load: loads routes from file on startup 39 | * * loadFile: speficies name of file to load 40 | * * logLevel: sets the log level for the app 41 | * 42 | */ 43 | livePersister.persistRoutes = function( opts ){ 44 | 45 | // Import Modules and Configure Input and Output streams 46 | var reconnect = undefined // holds timer that maintains server connection 47 | ; 48 | 49 | var port = opts.port || 9000 50 | , host = opts.host || "localhost" 51 | , secure = opts.secure || false 52 | , autosave = opts.autosave || true 53 | , load = opts.load || true 54 | , configFile = opts.loadFile || "live_persist_config.json" 55 | ; 56 | 57 | var clients = [] 58 | , routes = [] 59 | ; 60 | 61 | logger.debugLevel = opts.logLevel || "error"; 62 | 63 | /** 64 | * check if data/routes directory already exists, and if not, then create it. 65 | */ 66 | var setupLogDirectory = function () { 67 | var data_dir = __dirname + "/data" 68 | , routes_dir = __dirname + "/data/routes" 69 | , live_dir = __dirname + "/data/routes/live" 70 | ; 71 | 72 | // check if data folder exists 73 | try { 74 | fs.statSync(data_dir); 75 | } 76 | catch (e) { 77 | fs.mkdir(data_dir, err => { if (err) console.log(err) }); 78 | logger.log("info", "creating data directory"); 79 | } 80 | 81 | // check if data/routes folder exists 82 | try { 83 | fs.statSync(routes_dir); 84 | } 85 | catch (e) { 86 | fs.mkdir(routes_dir, err => { if (err) console.log(err) }); 87 | logger.log("info", "creating data/routes directory"); 88 | } 89 | 90 | // check if data/routes folder exists 91 | try { 92 | fs.statSync(live_dir); 93 | } 94 | catch (e) { 95 | fs.mkdir(live_dir, err => { if (err) console.log(err) }); 96 | logger.log("info", "creating data/routes/live directory"); 97 | } 98 | } 99 | 100 | var setupWSClient = function(){ 101 | 102 | // create the wsclient to connect to spacebrew 103 | 104 | //{rejectUnauthorized: false} 105 | //check for secure socket 106 | if (secure){ 107 | logger.log("info", 'spacebrew_live_persist secure socket'); 108 | var options = { rejectUnauthorized: false } 109 | wsc = new WebSocketClient("wss://" + host + ":" + port, options); 110 | }else{ 111 | wsc = new WebSocketClient("ws://" + host + ":" + port); 112 | } 113 | 114 | // configure the spacebrew admin client once connection stablished 115 | wsc.on("open", function(conn){ 116 | logger.log("info", "[ws.open] connected to spacebrew \n"); 117 | 118 | // send the admin configuration message to spacebrew server 119 | var adminMsg = { "admin": [ 120 | { 121 | "admin": true 122 | , "no_msgs" : true 123 | } 124 | ]}; 125 | wsc.send(JSON.stringify(adminMsg)); 126 | 127 | 128 | // if the reconnect timer is activated then stop it 129 | if (reconnect) { 130 | reconnect = clearInterval(reconnect); 131 | reconnect = undefined; 132 | } 133 | 134 | // load the routes that were saved 135 | if (load) loadRoutes(); 136 | }); 137 | 138 | // handle messages 139 | wsc.on("message", receivedMessage); 140 | 141 | // handle websocket error events 142 | wsc.on("error", function(){ 143 | logger.log("error", "[ws.onerror] spacebrew Connection Error"); 144 | logger.log("error", arguments); 145 | if (wsc.readyState != WS_OPEN) reestablishConnection(); 146 | }); 147 | 148 | // handle websocket close events 149 | wsc.on("close", function(){ 150 | logger.log("info", "[on.close] spacebrew Connection Closed"); 151 | logger.log("info", arguments); 152 | reestablishConnection(); 153 | }); 154 | } 155 | 156 | /** 157 | * Walks all the clients and all the persistent routes, and sends a route Add message for each 158 | * route that should exist. 159 | */ 160 | var ensureConnected = function(){ 161 | //for each publisher, if that publisher is in the persistent routes 162 | // for each subscriber, if that subscriber is the other end of that persistent route 163 | // send the add route message 164 | 165 | //for each publisher 166 | for (var i = 0; i < clients.length; i++){ 167 | for (var j = 0; j < clients[i].publish.messages.length; j++){ 168 | // for each persistent route 169 | for (var k = 0; k < routes.length; k++){ 170 | var currRoute = routes[k]; 171 | 172 | // if the publisher is in a persistent route 173 | // ---- TO DO: check if the client remoteAddress also matches 174 | if (currRoute.publisher.clientName === clients[i].name && 175 | currRoute.publisher.remoteAddress === clients[i].remoteAddress && 176 | currRoute.publisher.name === clients[i].publish.messages[j].name){ 177 | 178 | // loop through all connected clients to find a subscriber 179 | for (var m = 0; m < clients.length; m++){ 180 | // loop through subscribers for each client 181 | for (var n = 0; n < clients[m].subscribe.messages.length; n++){ 182 | 183 | // if a client/subscriber match is found then add route 184 | // ---- TO DO: check if the client remoteAddress also matches 185 | if (currRoute.subscriber.clientName === clients[m].name && 186 | currRoute.subscriber.remoteAddress === clients[m].remoteAddress && 187 | currRoute.subscriber.name === clients[m].subscribe.messages[n].name){ 188 | 189 | //if the pub/sub pair match the persistent route 190 | //send route message 191 | wsc.send(JSON.stringify({ 192 | route:{ 193 | type:'add' 194 | , publisher: { 195 | clientName:clients[i].name 196 | , name:clients[i].publish.messages[j].name 197 | , type:clients[i].publish.messages[j].type 198 | , remoteAddress:clients[i].remoteAddress 199 | } 200 | , subscriber:{ 201 | clientName:clients[m].name 202 | , name:clients[m].subscribe.messages[n].name 203 | , type:clients[m].subscribe.messages[n].type 204 | , remoteAddress:clients[m].remoteAddress 205 | } 206 | } 207 | })); 208 | } 209 | } 210 | } 211 | } 212 | } 213 | } 214 | } 215 | }; 216 | 217 | /** 218 | * Called when we receive a message from the Server. 219 | * @param {websocket message} data The websocket message from the Server 220 | */ 221 | var receivedMessage = function(data){ 222 | logger.log("info", "[receivedMessage] received new message from spacebrew server: ") 223 | logger.log("info", data); 224 | 225 | if (data){ 226 | var json = JSON.parse(data) 227 | , status = handleMessage(json) 228 | ; 229 | 230 | if (status > 0){ 231 | for(var i = 0, end = json.length; i < end; i++){ 232 | handleMessage(json[i]); 233 | } 234 | } 235 | 236 | else if (status < 0) { 237 | logger.log("info", "[receivedMessage] message is not valid: "); 238 | logger.log("info", data); 239 | } 240 | } 241 | }; 242 | 243 | /** 244 | * Handle the json data from the Server and forward it to the appropriate function 245 | * @param {json} json The message sent from the Server 246 | * @return {boolean} True iff the message was a recognized type 247 | */ 248 | var handleMessage = function(json){ 249 | if (json.message || json.admin){ 250 | //do nothing 251 | } 252 | 253 | else if (json.config){ 254 | logger.log("info", "[handleMessage] add client request received: "); 255 | logger.log("info", json); 256 | handleConfigMessage(json); 257 | } 258 | 259 | else if (json.route){ 260 | logger.log("info", "[handleMessage] route request received: "); 261 | logger.log("info", json); 262 | if (json.route.type === 'remove'){ 263 | handleRouteRemoveMessage(json); 264 | } 265 | if (json.route.type === 'add'){ 266 | handleRouteAddMessage(json); 267 | } 268 | } 269 | 270 | else if (json.remove){ 271 | handleClientRemoveMessage(json); 272 | } 273 | 274 | // return read array status 275 | else if (json instanceof Array) { 276 | return 1; 277 | } 278 | 279 | // return failure status 280 | else { 281 | return -1; 282 | } 283 | 284 | // return success status 285 | return 0; 286 | }; 287 | 288 | /** 289 | * Handles removes routes from the spacebrew server 290 | * @param {json} msg The route remove message from the Server 291 | */ 292 | var handleRouteRemoveMessage = function(msg){ 293 | // if the route remove message was associated to client disconnecting, then don't 294 | // delete from the persistent route list. 295 | if ( msg.route.client_disconnect ) return; 296 | 297 | logger.log("info", "[handleRouteRemoveMessage] request received "); 298 | logger.log("info", msg); 299 | 300 | for (var i = routes.length - 1; i >= 0; i--) { 301 | var currRoute = routes[i]; 302 | if (currRoute.publisher.clientName === msg.route.publisher.clientName && 303 | currRoute.publisher.name === msg.route.publisher.name && 304 | currRoute.publisher.remoteAddress === msg.route.publisher.remoteAddress && 305 | currRoute.subscriber.clientName == msg.route.subscriber.clientName && 306 | currRoute.subscriber.name === msg.route.subscriber.name && 307 | currRoute.subscriber.remoteAddress === msg.route.subscriber.remoteAddress){ 308 | 309 | // remove this route 310 | routes.splice(i,1); 311 | logger.log("info", "[handleRouteRemoveMessage] removed route at index " + i); 312 | } 313 | }; 314 | 315 | saveRoutes(); 316 | }; 317 | 318 | /** 319 | * Handles add routes from the spacebrew 320 | * @param {json} msg The route remove message from the Server 321 | */ 322 | var handleRouteAddMessage = function(msg){ 323 | 324 | logger.log("info", "[handleRouteAddMessage] request received "); 325 | logger.log("info", msg); 326 | 327 | //go through all persistent routes and see if this one exists already 328 | for (var i = routes.length - 1; i >= 0; i--) { 329 | var currRoute = routes[i]; 330 | if (currRoute.publisher.clientName === msg.route.publisher.clientName && 331 | currRoute.publisher.name === msg.route.publisher.name && 332 | currRoute.publisher.remoteAddress === msg.route.publisher.remoteAddress && 333 | currRoute.subscriber.clientName == msg.route.publisher.clientName && 334 | currRoute.subscriber.name === msg.route.publisher.name && 335 | currRoute.subscriber.remoteAddress === msg.route.publisher.remoteAddress){ 336 | 337 | //this route already exists, abort 338 | return; 339 | } 340 | }; 341 | 342 | //add the route to the list 343 | var newRoute = { 344 | publisher: msg.route.publisher, 345 | subscriber: msg.route.subscriber 346 | }; 347 | routes.push(newRoute); 348 | 349 | saveRoutes(); 350 | 351 | logger.log("info", "[handleRouteAddMessage] new route: "); 352 | logger.log("info", newRoute); 353 | } 354 | 355 | /** 356 | * Handles a remove message from the Server when a Client disconnects. 357 | * This function cleans up the appropriate data structures 358 | * @param {json} msg The message from the Server 359 | */ 360 | var handleClientRemoveMessage = function(msg){ 361 | for (var j = msg.remove.length-1; j >= 0; j--){ 362 | for (var i = clients.length - 1; i >= 0; i--){ 363 | if (areClientsEqual(clients[i], msg.remove[j])){ 364 | clients.splice(i, 1); 365 | logger.log("info", "[handleClientRemoveMessage] removed a client"); 366 | break; 367 | } 368 | } 369 | } 370 | }; 371 | 372 | /** 373 | * handles a new Config message from a Client. Will connect the new Client to 374 | * all the necessary persistent routes. 375 | * @param {json} msg The Config message from the Server from a Client 376 | */ 377 | var handleConfigMessage = function(msg){ 378 | var existing = false; 379 | 380 | // see if we are updating a current client 381 | for (var i = clients.length-1; i >= 0; i--){ 382 | if (areClientsEqual(clients[i], msg.config)){ 383 | //we are updating an existing client 384 | logger.log("info", "[handleConfigMessage] updating a client"); 385 | clients[i] = msg.config; 386 | existing = true; 387 | } 388 | } 389 | // otherwise add client to array 390 | if (!existing){ 391 | logger.log("info", "[handleConfigMessage] adding a new client"); 392 | clients.push(msg.config); 393 | } 394 | 395 | ensureConnected(); 396 | }; 397 | 398 | /** 399 | * Sends an 'add route' command to the Server. 400 | * @param {Client obj} pubClient The client of the publisher involved in the new route 401 | * @param {Pub obj} pub The particular publisher exposed by pubClient involved in the new route 402 | * @param {Client obj} subClient The client of the subscriber involved in the new route 403 | * @param {Pub obj} sub The particular subscriber exposed by subClient involved in the new route 404 | */ 405 | var addRoute = function(pubClient, pub, subClient, sub){ 406 | if (pub.type != sub.type){ 407 | return; 408 | } 409 | wsc.send(JSON.stringify({ 410 | route:{type:'add', 411 | publisher:{clientName:pubClient.name, 412 | name:pub.name, 413 | type:pub.type, 414 | remoteAddress:pubClient.remoteAddress}, 415 | subscriber:{clientName:subClient.name, 416 | name:sub.name, 417 | type:sub.type, 418 | remoteAddress:subClient.remoteAddress}} 419 | })); 420 | }; 421 | 422 | /** 423 | * Save routes to the file 424 | * @return {[type]} [description] 425 | */ 426 | var saveRoutes = function() { 427 | fs.writeFile('./data/routes/live/' + configFile, JSON.stringify(routes), function(err){ 428 | if (err){ 429 | logger.log("warn", "[saveRoutes] error saving route model to live_persist_config.json"); 430 | logger.log("warn", err); 431 | } else { 432 | logger.log("info", "[saveRoutes] route model was saved to live_persist_config.json"); 433 | } 434 | }); 435 | } 436 | 437 | /** 438 | * Loads routes from the specified file 439 | * 440 | * @param {String} filename Name of file that should be loaded 441 | * @return {Boolean} Returns true if data was properly loaded 442 | */ 443 | var loadRoutes = function(){ 444 | var raw_data; 445 | 446 | // open the raw_data file 447 | try{ 448 | raw_data = fs.readFileSync("./data/routes/live/" + configFile); 449 | } catch(err){ 450 | logger.log("warn", "[loadRoutes] live routes config file does not exist " + configFile); 451 | return false; 452 | } 453 | 454 | // parse the file contents 455 | try{ 456 | routes = JSON.parse(raw_data); 457 | ensureConnected(); 458 | }catch(err){ 459 | logger.log("warn", "[loadRoutes] unable to parse content from file "); 460 | return false; 461 | } 462 | 463 | return true; 464 | }; 465 | 466 | /** 467 | * Sets a timer for attempting to reconnect to the Spacbrew server again. Method is 468 | * called when connection to Spacebrew server is closed. 469 | */ 470 | var reestablishConnection = function() { 471 | if (!reconnect) { 472 | reconnect = setInterval(function() { 473 | if (wsc.readyState !== WS_OPEN) { 474 | logger.log("info", "[reestablishConnection] attempting to reconnect"); 475 | wsc.terminate(); 476 | setupWSClient(); 477 | } 478 | }, 5000); 479 | } 480 | } 481 | 482 | /** 483 | * Utility function for helping determine if two config objects refer to the same Client 484 | * @param {Client config} A 485 | * @param {Client config} B 486 | * @return {boolean} true iff the names and remote addresses match 487 | */ 488 | var areClientsEqual = function(A, B){ 489 | return A.name === B.name && A.remoteAddress === B.remoteAddress; 490 | }; 491 | 492 | setupLogDirectory(); 493 | setupWSClient(); 494 | } --------------------------------------------------------------------------------