├── 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 |
15 |
16 |
/About
17 |
/Contact
18 |
Developed by

19 |
20 |
21 |
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 | }
--------------------------------------------------------------------------------