├── .gitignore
├── ext
├── favicon.ico
├── 32_home_lock_open.png
├── 64_home_lock_open.png
├── apple-touch-icon.png
├── glyphicons-halflings.png
├── apple-touch-icon-57x57.png
├── apple-touch-icon-60x60.png
├── apple-touch-icon-72x72.png
├── apple-touch-icon-76x76.png
├── apple-touch-icon-114x114.png
├── apple-touch-icon-120x120.png
├── apple-touch-icon-144x144.png
├── apple-touch-icon-152x152.png
├── glyphicons-halflings-white.png
├── livestamp.min.js
├── sco.message.js
├── alertify.core.css
├── alertify.bootstrap.css
├── alarmserver.js
├── alertify.min.js
├── fastclick.min.js
├── index.html
├── bootstrap-responsive.min.css
├── moment.min.js
└── bootstrap.min.js
├── ubuntu-initscript
├── server.key
├── server.crt
├── alarmserver-example.cfg
├── README.md
├── HTTPChannel.py
├── Envisalink.py
├── envisalinkdefs.py
└── alarmserver.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
3 | alarmserver.cfg
4 | .idea
5 |
--------------------------------------------------------------------------------
/ext/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/favicon.ico
--------------------------------------------------------------------------------
/ext/32_home_lock_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/32_home_lock_open.png
--------------------------------------------------------------------------------
/ext/64_home_lock_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/64_home_lock_open.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon.png
--------------------------------------------------------------------------------
/ext/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/glyphicons-halflings.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/ext/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/ext/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/AlarmServer/master/ext/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/ubuntu-initscript:
--------------------------------------------------------------------------------
1 | version "1.0"
2 | author "Daniel Leaberry"
3 |
4 | start on filesystem and net-device-up IFACE!=lo
5 | stop on stopping network-interface INTERFACE=eth0
6 | stop on starting shutdown
7 |
8 | respawn
9 | console output
10 | exec /usr/bin/python /path/to/alarmserver.py -c /path/to/alarmserver.cfg
11 |
--------------------------------------------------------------------------------
/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXQIBAAKBgQDQ4xHHvPjESHOPpUTkTw/azfeC3CYz/oET9I58ffFxglQfWgHf
3 | 0EDMb77OYp01WIZ6DCGeOC1lhDdPSbxnkEz5V3EC0+xGnh5WB9X2T5Gekc6fDwgh
4 | 7pvgwCG0AFVqqzUVLwB1kCy5S4RLOVnyDdFnv5cnCP00oGmmh6aD3UnuTwIDAQAB
5 | AoGBAM9BTUXyAJ24CTjwUQ96Ro/hpoAncJxMG8Qx9SIeT+5A0rictJld5r0w7o+W
6 | Fsd0Q1FbMgvrT1eXPM2lqpLTARrjtgQglUHjwYC0wrFWpFgXBrIV/BSQYxK20iga
7 | TCDPjI2aANZpho2gglzO8/EMOYVQoYdCvnAn6ksqzT2wnE3BAkEA/gnjF56RdrI+
8 | jbTElM/GuT4pt8XWV1vUXVVqdXnfOjbwWf8tv2+QG4ebOjYk5sIZEpeGQaQuvH5C
9 | vj957VKAbwJBANJ/8G5HlHMg0MGzqP16V2PIkTo+NeN2ig2/pmS+O9NlM2SDS+yT
10 | w2bAAo+hd/7pmd7xeqT0rK+BUPjAJE2+oCECQBCr+3BYYrmEdyB7pY8Sl7sefkRm
11 | QmvXRfeeHG97QRAj/OAbJBh1LOLxollOpltSj6ytrwztxnduXdj4d3sAuBcCQAJy
12 | jl8Z4fX8ubCm4B4iYAW+/UFKG+JLLIvAYLTnKVbp5FEU3bsgdLMrJFx7KiQCn1Fi
13 | SWTFm4Rm4oQh58onn0ECQQDm7dsB6rx4T37lThWE4ZVDpYZ2X2xpQcp0sjCiMXnX
14 | U1KeT0WmaUTTI2iMGKtdablPfnLfFBlQe5eUgO6Fo4KZ
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICcTCCAdoCCQDKpfJYG8ha3zANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJD
3 | QTEQMA4GA1UECBMHT250YXJpbzEPMA0GA1UEBxMGT3R0YXdhMRQwEgYDVQQKEwtB
4 | bGFybVNlcnZlcjEUMBIGA1UEAxMLQWxhcm1TZXJ2ZXIxHzAdBgkqhkiG9w0BCQEW
5 | EGRvbm55a0BnbWFpbC5jb20wHhcNMTIxMjE1MDU1MTQxWhcNMTMxMjE1MDU1MTQx
6 | WjB9MQswCQYDVQQGEwJDQTEQMA4GA1UECBMHT250YXJpbzEPMA0GA1UEBxMGT3R0
7 | YXdhMRQwEgYDVQQKEwtBbGFybVNlcnZlcjEUMBIGA1UEAxMLQWxhcm1TZXJ2ZXIx
8 | HzAdBgkqhkiG9w0BCQEWEGRvbm55a0BnbWFpbC5jb20wgZ8wDQYJKoZIhvcNAQEB
9 | BQADgY0AMIGJAoGBANDjEce8+MRIc4+lRORPD9rN94LcJjP+gRP0jnx98XGCVB9a
10 | Ad/QQMxvvs5inTVYhnoMIZ44LWWEN09JvGeQTPlXcQLT7EaeHlYH1fZPkZ6Rzp8P
11 | CCHum+DAIbQAVWqrNRUvAHWQLLlLhEs5WfIN0We/lycI/TSgaaaHpoPdSe5PAgMB
12 | AAEwDQYJKoZIhvcNAQEFBQADgYEASqwreYMWeBt/QMWQwGqWT59KuqACTHvY5d8l
13 | cItR8tDXAm19x4tiBxMOKuvzq0CQ9mQXk5Zbq2A1joFby2pfDYHerABhVBG9Xzar
14 | bDb4ARbKJIpcr/UtEu1E1cw7/NL2ruo+B4savunCiNgs9cDyFhmwxc6pz7zGFj1x
15 | v9XYFUs=
16 | -----END CERTIFICATE-----
17 |
--------------------------------------------------------------------------------
/ext/livestamp.min.js:
--------------------------------------------------------------------------------
1 | // Livestamp.js / v1.1.2 / (c) 2012 Matt Bradley / MIT License
2 | (function(d,g){var h=1E3,i=!1,e=d([]),j=function(b,a){var c=b.data("livestampdata");"number"==typeof a&&(a*=1E3);b.removeAttr("data-livestamp").removeData("livestamp");a=g(a);g.isMoment(a)&&!isNaN(+a)&&(c=d.extend({},{original:b.contents()},c),c.moment=g(a),b.data("livestampdata",c).empty(),e.push(b[0]))},k=function(){i||(f.update(),setTimeout(k,h))},f={update:function(){d("[data-livestamp]").each(function(){var a=d(this);j(a,a.data("livestamp"))});var b=[];e.each(function(){var a=d(this),c=a.data("livestampdata");
3 | if(void 0===c)b.push(this);else if(g.isMoment(c.moment)){var e=a.html(),c=c.moment.fromNow();if(e!=c){var f=d.Event("change.livestamp");a.trigger(f,[e,c]);f.isDefaultPrevented()||a.html(c)}}});e=e.not(b)},pause:function(){i=!0},resume:function(){i=!1;k()},interval:function(b){if(void 0===b)return h;h=b}},l={add:function(b,a){"number"==typeof a&&(a*=1E3);a=g(a);g.isMoment(a)&&!isNaN(+a)&&(b.each(function(){j(d(this),a)}),f.update());return b},destroy:function(b){e=e.not(b);b.each(function(){var a=
4 | d(this),c=a.data("livestampdata");if(void 0===c)return b;a.html(c.original?c.original:"").removeData("livestampdata")});return b},isLivestamp:function(b){return void 0!==b.data("livestampdata")}};d.livestamp=f;d(function(){f.resume()});d.fn.livestamp=function(b,a){l[b]||(a=b,b="add");return l[b](this,a)}})(jQuery,moment);
5 |
--------------------------------------------------------------------------------
/alarmserver-example.cfg:
--------------------------------------------------------------------------------
1 | [alarmserver]
2 | ## If a filename is given all output will be logged to the filename.
3 | ## If left blank output will all be on the console
4 | #logfile=/full/path/to/output.log
5 | logfile=
6 |
7 | ## Log URL requests
8 | ## By default all the url requests are logged. These happen every 5
9 | ## seconds with the web ui's. To disable all these set this to False
10 | logurlrequests=True
11 |
12 |
13 | ## The server runs with SSL. You need a certificate and key
14 | ## server.crt and server.key are included but you should
15 | ## generate your own.
16 | ## If left blank the default included cert/key will be used
17 | #certfile=/etc/apache2/ssl/server.crt
18 | #keyfile=/etc/apache2/ssl/server.key
19 | certfile=
20 | keyfile=
21 |
22 | ## Maximum number of events to display for each zone
23 | maxevents=10
24 |
25 | ## Total number of events to show for all the zones combined
26 | maxallevents=100
27 |
28 | ## Port to run the server on
29 | httpsport=8111
30 |
31 | ## Use a fuzzy time algorithm for displaying dates and times
32 | ## True means times will be "4 minutes ago", "3 days ago"
33 | ## False means times will be exact "Jun 21st 2013 08:00:00"
34 | eventtimeago=True
35 |
36 | ## Name of your parition(s)
37 | partition1=Home
38 |
39 | ## Zone names. Delete the zones you're not using to have them hidden.
40 | ## Add more zoneXX if you need more zones
41 | zone1=A
42 | zone2=B
43 | zone3=C
44 | zone4=D
45 | zone5=E
46 | zone6=F
47 | zone7=G
48 | zone8=H
49 | zone9=I
50 | zone10=J
51 | zone11=K
52 | zone12=L
53 | zone13=M
54 | zone14=N
55 | zone15=O
56 | zone16=P
57 |
58 | ## Pretty names for the user ids that arm/disarm alarm.
59 | user1=MyUser1
60 | user2=MyUser2
61 | user3=MyUser3
62 |
63 | [pushover]
64 | enable=False
65 | usertoken=tokengoeshere
66 |
67 | [envisalink]
68 | ## Connection credentials to talk to the Envisalink device
69 | host=envisalink
70 | port=4025
71 | pass=user
72 |
73 | ## Run a proxy for the Envisalink device to get around the 1 connection limit
74 | enableproxy=True
75 | proxyport=4025
76 | proxypass=user
77 |
78 | ## Alarm code: If defined you can disarm the alarm without having to
79 | ## enter a code.
80 | alarmcode=1111
81 |
--------------------------------------------------------------------------------
/ext/sco.message.js:
--------------------------------------------------------------------------------
1 | /* ==========================================================
2 | * sco.message.js
3 | * http://github.com/terebentina/sco.js
4 | * ==========================================================
5 | * Copyright 2013 Dan Caragea.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================== */
19 |
20 | /*jshint laxcomma:true, sub:true, browser:true, jquery:true, eqeqeq: false */
21 |
22 | ;(function($, undefined) {
23 | "use strict";
24 |
25 | var pluginName = 'scojs_message';
26 |
27 | $[pluginName] = function(message, type) {
28 | clearTimeout($[pluginName].timeout);
29 | var $selector = $('#' + $[pluginName].options.id);
30 | if (!$selector.length) {
31 | $selector = $('
', {id: $[pluginName].options.id}).appendTo($[pluginName].options.appendTo);
32 | }
33 | $selector.html(message);
34 | if (type === undefined || type == $[pluginName].TYPE_ERROR) {
35 | $selector.removeClass($[pluginName].options.okClass).addClass($[pluginName].options.errClass);
36 | } else if (type == $[pluginName].TYPE_OK) {
37 | $selector.removeClass($[pluginName].options.errClass).addClass($[pluginName].options.okClass);
38 | }
39 | $selector.slideDown('fast', function() {
40 | $[pluginName].timeout = setTimeout(function() { $selector.slideUp('fast'); }, $[pluginName].options.delay);
41 | });
42 | };
43 |
44 |
45 | $.extend($[pluginName], {
46 | options: {
47 | id: 'page_message'
48 | ,okClass: 'page_mess_ok'
49 | ,errClass: 'page_mess_error'
50 | ,delay: 4000
51 | ,appendTo: 'body' // where should the modal be appended to (default to document.body). Added for unit tests, not really needed in real life.
52 | },
53 |
54 | TYPE_ERROR: 1,
55 | TYPE_OK: 2
56 | });
57 | })(jQuery);
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is still beta software.
2 |
3 | The ssl certificates that are provided are intended for demo purposes only.
4 | Please use openssl to generate your own. A quick HOWTO is below.
5 |
6 | As with any project documentation is key, there is plenty more to go in here and
7 | it will hopefully be soon!
8 |
9 | Config:
10 | Please see the alarmserver-example.cfg and rename to alarmserver.cfg and
11 | customize to requirements.
12 |
13 |
14 | Web Interface
15 | -------------
16 | The web interface uses a responsive design which limits the scrolling on both desktop and mobile.
17 |
18 | ### Desktop ###
19 | 
20 |
21 | ### Mobile ###
22 | 
23 |
24 |
25 | OpenSSL Certificate Howto
26 | -------------------
27 |
28 | To generate a self signed cert issue the following in a command prompt:
29 | `openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout server.key -out server.crt`
30 |
31 | Openssl will ask you some questions. The only semi-important one is the 'common name' field.
32 | You want this set to your servers fqdn. IE alarmserver.example.com.
33 |
34 | If you have a real ssl cert from a certificate authority and it has intermediate certs then you'll need to bundle them all up or the webbrowser will complain about it not being a valid cert. To bundle the certs use cat to include your cert, then the intermediates (ie cat mycert.crt > combined.crt; cat intermediates.crt >> combined.crt)
35 |
36 |
37 | Dependencies:
38 | -------------
39 |
40 | On windows, pyOpenSSL is required.
41 | http://pypi.python.org/pypi/pyOpenSSL
42 |
43 |
44 | Launchers
45 | ---------
46 | * [MacOSX](https://github.com/gschrader/Alarm-Server-Launcher)
47 |
48 | REST API Info
49 | -------------
50 |
51 | */api*
52 |
53 | * Returns a JSON dump of all currently known states
54 |
55 | */api/alarm/arm*
56 |
57 | * Quick arm
58 |
59 | */api/alarm/armwithcode?alarmcode=1111*
60 |
61 | * Arm with a code
62 | * Required param = **alarmcode**
63 |
64 | */api/alarm/stayarm*
65 |
66 | * Stay arm, no code needed
67 |
68 | */api/alarm/disarm*
69 |
70 | * Disarm system
71 | * Optional param = **alarmcode**
72 | * If alarmcode param is missing the config file value is used instead
73 |
74 | */api/pgm*
75 |
76 | * Activate a PGM output:
77 | * Required param = **pgmnum**
78 | * Required param = **alarmcode**
79 |
80 | */api/refresh*
81 |
82 | * Refresh data from alarm panel
83 |
84 | */api/config/eventtimeago*
85 |
86 | * Returns status of eventtimeago from the config file
87 |
88 |
--------------------------------------------------------------------------------
/HTTPChannel.py:
--------------------------------------------------------------------------------
1 | import asyncore, asynchat
2 | import StringIO, mimetools
3 | import string
4 | import os, sys
5 | import datetime
6 |
7 | class push_FileProducer:
8 | # a producer which reads data from a file object
9 |
10 | def __init__(self, file):
11 | self.file = open(file, "rb")
12 |
13 | def more(self):
14 | if self.file:
15 | data = self.file.read(2048)
16 | if data:
17 | return data
18 | self.file = None
19 | return ""
20 |
21 | class HTTPChannel(asynchat.async_chat):
22 | def __init__(self, server, sock, addr):
23 | asynchat.async_chat.__init__(self, sock)
24 | self.server = server
25 | self.set_terminator("\r\n\r\n")
26 | self.header = None
27 | self.data = ""
28 | self.shutdown = 0
29 |
30 | def collect_incoming_data(self, data):
31 | self.data = self.data + data
32 | if len(self.data) > 16384:
33 | # limit the header size to prevent attacks
34 | self.shutdown = 1
35 |
36 | def found_terminator(self):
37 | if not self.header:
38 | # parse http header
39 | fp = StringIO.StringIO(self.data)
40 | request = string.split(fp.readline(), None, 2)
41 | if len(request) != 3:
42 | # badly formed request; just shut down
43 | self.shutdown = 1
44 | else:
45 | # parse message header
46 | self.header = mimetools.Message(fp)
47 | self.set_terminator("\r\n")
48 | self.server.handle_request(
49 | self, request[0], request[1], self.header
50 | )
51 | self.close_when_done()
52 | self.data = ""
53 | else:
54 | pass # ignore body data, for now
55 |
56 | def pushstatus(self, status, explanation="OK"):
57 | self.push("HTTP/1.0 %d %s\r\n" % (status, explanation))
58 |
59 | def pushok(self, content):
60 | self.pushstatus(200, "OK")
61 | self.push('Content-type: application/json\r\n')
62 | self.push('Expires: Sat, 26 Jul 1997 05:00:00 GMT\r\n')
63 | self.push('Last-Modified: '+ datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")+' GMT\r\n')
64 | self.push('Cache-Control: no-store, no-cache, must-revalidate\r\n' )
65 | self.push('Cache-Control: post-check=0, pre-check=0\r\n')
66 | self.push('Pragma: no-cache\r\n' )
67 | self.push('\r\n')
68 | self.push(content)
69 |
70 | def pushfile(self, file):
71 | self.pushstatus(200, "OK")
72 | extension = os.path.splitext(file)[1]
73 | if extension == ".html":
74 | self.push("Content-type: text/html\r\n")
75 | elif extension == ".js":
76 | self.push("Content-type: text/javascript\r\n")
77 | elif extension == ".png":
78 | self.push("Content-type: image/png\r\n")
79 | elif extension == ".css":
80 | self.push("Content-type: text/css\r\n")
81 | self.push("\r\n")
82 | self.push_with_producer(push_FileProducer(sys.path[0] + os.sep + 'ext' + os.sep + file))
83 |
84 |
--------------------------------------------------------------------------------
/ext/alertify.core.css:
--------------------------------------------------------------------------------
1 | .alertify,
2 | .alertify-show,
3 | .alertify-log {
4 | -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
5 | -moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
6 | -ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
7 | -o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
8 | transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */
9 | }
10 | .alertify-hide {
11 | -webkit-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
12 | -moz-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
13 | -ms-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
14 | -o-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
15 | transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */
16 | }
17 | .alertify-log-hide {
18 | -webkit-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
19 | -moz-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
20 | -ms-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
21 | -o-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
22 | transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */
23 | }
24 | .alertify-cover {
25 | position: fixed; z-index: 99999;
26 | top: 0; right: 0; bottom: 0; left: 0;
27 | background-color:white;
28 | filter:alpha(opacity=0);
29 | opacity:0;
30 | }
31 | .alertify-cover-hidden {
32 | display: none;
33 | }
34 | .alertify {
35 | position: fixed; z-index: 99999;
36 | top: 50px; left: 50%;
37 | width: 550px;
38 | margin-left: -275px;
39 | opacity: 1;
40 | }
41 | .alertify-hidden {
42 | -webkit-transform: translate(0,-150px);
43 | -moz-transform: translate(0,-150px);
44 | -ms-transform: translate(0,-150px);
45 | -o-transform: translate(0,-150px);
46 | transform: translate(0,-150px);
47 | opacity: 0;
48 | display: none;
49 | }
50 | /* overwrite display: none; for everything except IE6-8 */
51 | :root *> .alertify-hidden {
52 | display: block;
53 | visibility: hidden;
54 | }
55 | .alertify-logs {
56 | position: fixed;
57 | z-index: 5000;
58 | bottom: 10px;
59 | right: 10px;
60 | width: 300px;
61 | }
62 | .alertify-logs-hidden {
63 | display: none;
64 | }
65 | .alertify-log {
66 | display: block;
67 | margin-top: 10px;
68 | position: relative;
69 | right: -300px;
70 | opacity: 0;
71 | }
72 | .alertify-log-show {
73 | right: 0;
74 | opacity: 1;
75 | }
76 | .alertify-log-hide {
77 | -webkit-transform: translate(300px, 0);
78 | -moz-transform: translate(300px, 0);
79 | -ms-transform: translate(300px, 0);
80 | -o-transform: translate(300px, 0);
81 | transform: translate(300px, 0);
82 | opacity: 0;
83 | }
84 | .alertify-dialog {
85 | padding: 25px;
86 | }
87 | .alertify-resetFocus {
88 | border: 0;
89 | clip: rect(0 0 0 0);
90 | height: 1px;
91 | margin: -1px;
92 | overflow: hidden;
93 | padding: 0;
94 | position: absolute;
95 | width: 1px;
96 | }
97 | .alertify-inner {
98 | text-align: center;
99 | }
100 | .alertify-text {
101 | margin-bottom: 15px;
102 | width: 100%;
103 | -webkit-box-sizing: border-box;
104 | -moz-box-sizing: border-box;
105 | box-sizing: border-box;
106 | font-size: 100%;
107 | }
108 | .alertify-buttons {
109 | }
110 | .alertify-button,
111 | .alertify-button:hover,
112 | .alertify-button:active,
113 | .alertify-button:visited {
114 | background: none;
115 | text-decoration: none;
116 | border: none;
117 | /* line-height and font-size for input button */
118 | line-height: 1.5;
119 | font-size: 100%;
120 | display: inline-block;
121 | cursor: pointer;
122 | margin-left: 5px;
123 | }
124 |
125 | @media only screen and (max-width: 680px) {
126 | .alertify,
127 | .alertify-logs {
128 | width: 90%;
129 | -webkit-box-sizing: border-box;
130 | -moz-box-sizing: border-box;
131 | box-sizing: border-box;
132 | }
133 | .alertify {
134 | left: 5%;
135 | margin: 0;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/ext/alertify.bootstrap.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Twitter Bootstrap Look and Feel
3 | * Based on http://twitter.github.com/bootstrap/
4 | */
5 | .alertify,
6 | .alertify-log {
7 | font-family: sans-serif;
8 | }
9 | .alertify {
10 | background: #FFF;
11 | border: 1px solid #8E8E8E; /* browsers that don't support rgba */
12 | border: 1px solid rgba(0,0,0,.3);
13 | border-radius: 6px;
14 | box-shadow: 0 3px 7px rgba(0,0,0,.3);
15 | -webkit-background-clip: padding; /* Safari 4? Chrome 6? */
16 | -moz-background-clip: padding; /* Firefox 3.6 */
17 | background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */
18 | }
19 | .alertify-dialog {
20 | padding: 0;
21 | }
22 | .alertify-inner {
23 | text-align: left;
24 | }
25 | .alertify-message {
26 | padding: 15px;
27 | margin: 0;
28 | }
29 | .alertify-text-wrapper {
30 | padding: 0 15px;
31 | }
32 | .alertify-text {
33 | color: #555;
34 | border-radius: 4px;
35 | padding: 8px;
36 | background-color: #FFF;
37 | border: 1px solid #CCC;
38 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
39 | }
40 | .alertify-text:focus {
41 | border-color: rgba(82,168,236,.8);
42 | outline: 0;
43 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);
44 | }
45 |
46 | .alertify-buttons {
47 | padding: 14px 15px 15px;
48 | background: #F5F5F5;
49 | border-top: 1px solid #DDD;
50 | border-radius: 0 0 6px 6px;
51 | box-shadow: inset 0 1px 0 #FFF;
52 | text-align: right;
53 | }
54 | .alertify-button,
55 | .alertify-button:hover,
56 | .alertify-button:focus,
57 | .alertify-button:active {
58 | margin-left: 10px;
59 | border-radius: 4px;
60 | font-weight: normal;
61 | padding: 4px 12px;
62 | text-decoration: none;
63 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 1px 2px rgba(0, 0, 0, .05);
64 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
65 | background-image: -moz-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
66 | background-image: -ms-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
67 | background-image: -o-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
68 | background-image: linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
69 | }
70 | .alertify-button:focus {
71 | outline: none;
72 | box-shadow: 0 0 5px #2B72D5;
73 | }
74 | .alertify-button:active {
75 | position: relative;
76 | box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
77 | }
78 | .alertify-button-cancel,
79 | .alertify-button-cancel:hover,
80 | .alertify-button-cancel:focus,
81 | .alertify-button-cancel:active {
82 | text-shadow: 0 -1px 0 rgba(255,255,255,.75);
83 | background-color: #E6E6E6;
84 | border: 1px solid #BBB;
85 | color: #333;
86 | background-image: -webkit-linear-gradient(top, #FFF, #E6E6E6);
87 | background-image: -moz-linear-gradient(top, #FFF, #E6E6E6);
88 | background-image: -ms-linear-gradient(top, #FFF, #E6E6E6);
89 | background-image: -o-linear-gradient(top, #FFF, #E6E6E6);
90 | background-image: linear-gradient(top, #FFF, #E6E6E6);
91 | }
92 | .alertify-button-cancel:hover,
93 | .alertify-button-cancel:focus,
94 | .alertify-button-cancel:active {
95 | background: #E6E6E6;
96 | }
97 | .alertify-button-ok,
98 | .alertify-button-ok:hover,
99 | .alertify-button-ok:focus,
100 | .alertify-button-ok:active {
101 | text-shadow: 0 -1px 0 rgba(0,0,0,.25);
102 | background-color: #04C;
103 | border: 1px solid #04C;
104 | border-color: #04C #04C #002A80;
105 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
106 | color: #FFF;
107 | }
108 | .alertify-button-ok:hover,
109 | .alertify-button-ok:focus,
110 | .alertify-button-ok:active {
111 | background: #04C;
112 | }
113 |
114 | .alertify-log {
115 | background: #D9EDF7;
116 | padding: 8px 14px;
117 | border-radius: 4px;
118 | color: #3A8ABF;
119 | text-shadow: 0 1px 0 rgba(255,255,255,.5);
120 | border: 1px solid #BCE8F1;
121 | }
122 | .alertify-log-error {
123 | color: #B94A48;
124 | background: #F2DEDE;
125 | border: 1px solid #EED3D7;
126 | }
127 | .alertify-log-success {
128 | color: #468847;
129 | background: #DFF0D8;
130 | border: 1px solid #D6E9C6;
131 | }
--------------------------------------------------------------------------------
/ext/alarmserver.js:
--------------------------------------------------------------------------------
1 | var activeTab = null;
2 | var activeCollapse = null;
3 | var timeago = true;
4 | var autorefresh = true;
5 | var cache = {};
6 |
7 | window.matchMediaPhone = function () {
8 | return matchMedia('(max-width: 767px)').matches;
9 | }
10 | window.matchMediaTablet = function () {
11 | return matchMedia('(min-width: 768px) and (max-width: 979px)').matches;
12 | }
13 | window.matchMediaDesktop = function () {
14 | return matchMedia('(min-width: 979px)').matches;
15 | }
16 |
17 | $.ajax({
18 | type: "GET",
19 | url: "/api/config/eventtimeago",
20 | contentType: "application/json; charset=utf-8",
21 | dataType: "json",
22 | data: "{}",
23 | success: function (res) {
24 | timeago = res.eventtimeago.toLowerCase() == "true";
25 | }
26 | });
27 |
28 | function createEvents(list) {
29 | var source = $("#events-template").html();
30 | var template = Handlebars.compile(source);
31 |
32 | list.reverse().forEach(function (ev, i) {
33 | ev.time = moment(ev.datetime).calendar();
34 | });
35 |
36 | return template({events: list, timeago: timeago});
37 | }
38 |
39 | function details(obj, templateId) {
40 | var source = $(templateId).html();
41 | var template = Handlebars.compile(source);
42 |
43 | var zones = [];
44 | for (var i = 1; i < 65; i++) {
45 | var zone = obj.zone[i + ''];
46 | if (zone && zone.name) {
47 | zone.id = i;
48 | zone.class = zone.status.open ? 'badge-important' : 'badge-success';
49 | zone.icon = !zone.status.open ? 'icon-ok-sign' : 'icon-minus-sign';
50 | zone.events = createEvents(zone.lastevents);
51 | zone.selected = "";
52 | if (activeCollapse == "collapseZone" + i) {
53 | zone.selected = "in";
54 | }
55 | zones.push(zone);
56 | }
57 | }
58 |
59 | var partitions = [];
60 | for (var i = 1; i < 65; i++) {
61 | var partition = obj.partition[i + ''];
62 | if (partition && partition.name) {
63 | partition.id = i;
64 | partition.class = partition.status.ready ? 'badge-success' : 'badge-important';
65 | partition.icon = partition.status.ready ? 'icon-ok-sign' : 'icon-minus-sign';
66 | partition.events = createEvents(partition.lastevents);
67 | partition.selected = "";
68 | if (activeCollapse == "collapsePart" + i) {
69 | partition.selected = "in";
70 | }
71 | partitions.push(partition);
72 | }
73 | }
74 |
75 | return template({zones: zones,
76 | zoneAllEvents: createEvents(obj.zone.lastevents),
77 | partitions: partitions,
78 | partitionAllEvents: createEvents(obj.partition.lastevents),
79 | zoneAllSelected: activeCollapse == "collapseZoneAll" ? "in" : "",
80 | partitionAllSelected: activeCollapse == "collapsePartAll" ? "in" : "",
81 | });
82 | }
83 |
84 | function actions(obj) {
85 | var source = $("#actions-template").html();
86 | var template = Handlebars.compile(source);
87 |
88 | return template({
89 | arm: !obj.partition["1"].status.armed && !obj.partition["1"].status.exit_delay,
90 | disarm: obj.partition["1"].status.armed,
91 | cancel: obj.partition["1"].status.exit_delay,
92 | pgm: obj.partition["1"].status.pgm_output
93 | });
94 | }
95 |
96 | function disarm() {
97 | alertify.prompt("What is your code?", function (e, code) {
98 | if (e) {
99 | doAction("/api/alarm/disarm?alarmcode=" + code);
100 | }
101 | });
102 | }
103 |
104 | function pgm() {
105 | alertify.prompt("Enter PGM # to trigger", function (e, pgmnum) {
106 | if (e) {
107 | alertify.prompt("What is your code?", function (e1, code) {
108 | if (e1) {
109 | doAction("/api/pgm?pgmnum=" + pgmnum + "&alarmcode=" + code);
110 | }
111 | });
112 | }
113 | });
114 | }
115 |
116 | function armwithcode() {
117 | alertify.prompt("What is your code?", function (e, code) {
118 | if (e) {
119 | console.log(code);
120 | doAction("/api/alarm/armwithcode?alarmcode=" + code);
121 | } else {
122 | alertify.error("not armed")
123 | }
124 | });
125 | }
126 |
127 | function doAction(action) {
128 | console.log(action);
129 | $.ajax({
130 | type: "GET",
131 | url: action,
132 | contentType: "application/json; charset=utf-8",
133 | dataType: "json",
134 | success: function (res) {
135 | console.log(res.response);
136 | alertify.success(res.response);
137 | },
138 | error: function () {
139 | alertify.error("error performing action");
140 | }
141 | });
142 | }
143 |
144 | function message(obj) {
145 | var str = '';
146 | if (obj.partition["1"].status.entry_delay) {
147 | str += 'Entry delay';
148 | }
149 |
150 | if (obj.partition["1"].status.exit_delay) {
151 | str += 'Exit delay';
152 | }
153 |
154 | if (obj.partition["1"].status.alarm) {
155 | str += 'Alarm';
156 | }
157 |
158 | if (obj.partition["1"].status.trouble) {
159 | str += 'Trouble';
160 | }
161 |
162 | if (obj.partition["1"].status.tamper) {
163 | str += 'Tamper';
164 | }
165 |
166 | if (obj.partition["1"].status.pgm_output) {
167 | str += 'PGM Output in progress';
168 | }
169 |
170 | $('#message').html(str).fadeIn();
171 | }
172 |
173 | function update(id, value, force) {
174 | var old = cache[id];
175 | if (old != value || force) {
176 | $(id).html(value).fadeIn();
177 | cache[id] = value;
178 | return true;
179 | }
180 | return false;
181 | }
182 |
183 | function refresh(force) {
184 | $.ajax({
185 | type: "GET",
186 | url: "/api",
187 | contentType: "application/json; charset=utf-8",
188 | dataType: "json",
189 | data: "{}",
190 | success: function (res) {
191 | if (matchMediaPhone()) {
192 | update('#mobile-details', details(res, "#mobile-template"), force);
193 | } else {
194 | if (update('#details', details(res, "#template")), force) {
195 | if (activeTab) {
196 | $('#tabs a[href="' + activeTab + '"]').tab('show');
197 | }
198 | }
199 | }
200 | update('#actions', actions(res), force);
201 | $('#tabs').tab();
202 |
203 | $('#tabs a[data-toggle="tab"]').on('shown', function (e) {
204 | activeTab = e.target.hash;
205 | });
206 | $('.accordion-body').on('show',function () {
207 | activeCollapse = this.id;
208 | }).on('hide', function () {
209 | activeCollapse = null;
210 | });
211 |
212 | if (autorefresh) {
213 | $("#autorefresh").addClass('active');
214 | }
215 |
216 | if (timeago) {
217 | $("#timeago").addClass('active');
218 | }
219 |
220 | message(res);
221 | }
222 | });
223 | }
224 |
225 | $(document).ready(function () {
226 | refresh();
227 | FastClick.attach(document.body);
228 | });
229 |
230 |
231 | setInterval(function () {
232 | if (autorefresh) {
233 | refresh(false);
234 | }
235 | }, 5000);
236 |
--------------------------------------------------------------------------------
/ext/alertify.min.js:
--------------------------------------------------------------------------------
1 | /*! alertify - v0.3.11 - 2013-10-08 */
2 | !function(a,b){"use strict";var c,d=a.document;c=function(){var c,e,f,g,h,i,j,k,l,m,n,o,p,q={},r={},s=!1,t={ENTER:13,ESC:27,SPACE:32},u=[];return r={buttons:{holder:'',submit:'',ok:'',cancel:''},input:'',message:'{{message}}
',log:'{{message}}'},p=function(){var a,c,e=!1,f=d.createElement("fakeelement"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"otransitionend",transition:"transitionend"};for(a in g)if(f.style[a]!==b){c=g[a],e=!0;break}return{type:c,supported:e}},c=function(a){return d.getElementById(a)},q={labels:{ok:"OK",cancel:"Cancel"},delay:5e3,buttonReverse:!1,buttonFocus:"ok",transition:b,addListeners:function(a){var b,c,i,j,k,l="undefined"!=typeof f,m="undefined"!=typeof e,n="undefined"!=typeof o,p="",q=this;b=function(b){return"undefined"!=typeof b.preventDefault&&b.preventDefault(),i(b),"undefined"!=typeof o&&(p=o.value),"function"==typeof a&&("undefined"!=typeof o?a(!0,p):a(!0)),!1},c=function(b){return"undefined"!=typeof b.preventDefault&&b.preventDefault(),i(b),"function"==typeof a&&a(!1),!1},i=function(){q.hide(),q.unbind(d.body,"keyup",j),q.unbind(g,"focus",k),l&&q.unbind(f,"click",b),m&&q.unbind(e,"click",c)},j=function(a){var d=a.keyCode;(d===t.SPACE&&!n||n&&d===t.ENTER)&&b(a),d===t.ESC&&m&&c(a)},k=function(){n?o.focus():!m||q.buttonReverse?f.focus():e.focus()},this.bind(g,"focus",k),this.bind(h,"focus",k),l&&this.bind(f,"click",b),m&&this.bind(e,"click",c),this.bind(d.body,"keyup",j),this.transition.supported||this.setFocus()},bind:function(a,b,c){"function"==typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c)},handleErrors:function(){if("undefined"!=typeof a.onerror){var b=this;return a.onerror=function(a,c,d){b.error("["+a+" on line "+d+" of "+c+"]",0)},!0}return!1},appendButtons:function(a,b){return this.buttonReverse?b+a:a+b},build:function(a){var b="",c=a.type,d=a.message,e=a.cssClass||"";switch(b+='",c){case"confirm":b=b.replace("{{buttons}}",this.appendButtons(r.buttons.cancel,r.buttons.ok)),b=b.replace("{{ok}}",this.labels.ok).replace("{{cancel}}",this.labels.cancel);break;case"prompt":b=b.replace("{{buttons}}",this.appendButtons(r.buttons.cancel,r.buttons.submit)),b=b.replace("{{ok}}",this.labels.ok).replace("{{cancel}}",this.labels.cancel);break;case"alert":b=b.replace("{{buttons}}",r.buttons.ok),b=b.replace("{{ok}}",this.labels.ok)}return l.className="alertify alertify-"+c+" "+e,k.className="alertify-cover",b},close:function(a,b){var c,d,e=b&&!isNaN(b)?+b:this.delay,f=this;this.bind(a,"click",function(){c(a)}),d=function(a){a.stopPropagation(),f.unbind(this,f.transition.type,d),m.removeChild(this),m.hasChildNodes()||(m.className+=" alertify-logs-hidden")},c=function(a){"undefined"!=typeof a&&a.parentNode===m&&(f.transition.supported?(f.bind(a,f.transition.type,d),a.className+=" alertify-log-hide"):(m.removeChild(a),m.hasChildNodes()||(m.className+=" alertify-logs-hidden")))},0!==b&&setTimeout(function(){c(a)},e)},dialog:function(a,b,c,e,f){j=d.activeElement;var g=function(){m&&null!==m.scrollTop&&k&&null!==k.scrollTop||g()};if("string"!=typeof a)throw new Error("message must be a string");if("string"!=typeof b)throw new Error("type must be a string");if("undefined"!=typeof c&&"function"!=typeof c)throw new Error("fn must be a function");return this.init(),g(),u.push({type:b,message:a,callback:c,placeholder:e,cssClass:f}),s||this.setup(),this},extend:function(a){if("string"!=typeof a)throw new Error("extend method must have exactly one paramter");return function(b,c){return this.log(b,a,c),this}},hide:function(){var a,b=this;u.splice(0,1),u.length>0?this.setup(!0):(s=!1,a=function(c){c.stopPropagation(),b.unbind(l,b.transition.type,a)},this.transition.supported?(this.bind(l,this.transition.type,a),l.className="alertify alertify-hide alertify-hidden"):l.className="alertify alertify-hide alertify-hidden alertify-isHidden",k.className="alertify-cover alertify-cover-hidden",j.focus())},init:function(){d.createElement("nav"),d.createElement("article"),d.createElement("section"),null==c("alertify-cover")&&(k=d.createElement("div"),k.setAttribute("id","alertify-cover"),k.className="alertify-cover alertify-cover-hidden",d.body.appendChild(k)),null==c("alertify")&&(s=!1,u=[],l=d.createElement("section"),l.setAttribute("id","alertify"),l.className="alertify alertify-hidden",d.body.appendChild(l)),null==c("alertify-logs")&&(m=d.createElement("section"),m.setAttribute("id","alertify-logs"),m.className="alertify-logs alertify-logs-hidden",d.body.appendChild(m)),d.body.setAttribute("tabindex","0"),this.transition=p()},log:function(a,b,c){var d=function(){m&&null!==m.scrollTop||d()};return this.init(),d(),m.className="alertify-logs",this.notify(a,b,c),this},notify:function(a,b,c){var e=d.createElement("article");e.className="alertify-log"+("string"==typeof b&&""!==b?" alertify-log-"+b:""),e.innerHTML=a,m.appendChild(e),setTimeout(function(){e.className=e.className+" alertify-log-show"},50),this.close(e,c)},set:function(a){var b;if("object"!=typeof a&&a instanceof Array)throw new Error("args must be an object");for(b in a)a.hasOwnProperty(b)&&(this[b]=a[b])},setFocus:function(){o?(o.focus(),o.select()):i.focus()},setup:function(a){var d,j=u[0],k=this;s=!0,d=function(a){a.stopPropagation(),k.setFocus(),k.unbind(l,k.transition.type,d)},this.transition.supported&&!a&&this.bind(l,this.transition.type,d),l.innerHTML=this.build(j),g=c("alertify-resetFocus"),h=c("alertify-resetFocusBack"),f=c("alertify-ok")||b,e=c("alertify-cancel")||b,i="cancel"===q.buttonFocus?e:"none"===q.buttonFocus?c("alertify-noneFocus"):f,o=c("alertify-text")||b,n=c("alertify-form")||b,"string"==typeof j.placeholder&&""!==j.placeholder&&(o.value=j.placeholder),a&&this.setFocus(),this.addListeners(j.callback)},unbind:function(a,b,c){"function"==typeof a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent&&a.detachEvent("on"+b,c)}},{alert:function(a,b,c){return q.dialog(a,"alert",b,"",c),this},confirm:function(a,b,c){return q.dialog(a,"confirm",b,"",c),this},extend:q.extend,init:q.init,log:function(a,b,c){return q.log(a,b,c),this},prompt:function(a,b,c,d){return q.dialog(a,"prompt",b,c,d),this},success:function(a,b){return q.log(a,"success",b),this},error:function(a,b){return q.log(a,"error",b),this},set:function(a){q.set(a)},labels:q.labels,debug:q.handleErrors}},"function"==typeof define?define([],function(){return new c}):"undefined"==typeof a.alertify&&(a.alertify=new c)}(this);
--------------------------------------------------------------------------------
/ext/fastclick.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | FastClick: polyfill to remove click delays on browsers with touch UIs.
3 |
4 | @version 0.6.8
5 | @codingstandard ftlabs-jsv2
6 | @copyright The Financial Times Limited [All Rights Reserved]
7 | @license MIT License (see LICENSE.txt)
8 | */
9 | function FastClick(a){var b,c=this;this.trackingClick=!1;this.trackingClickStart=0;this.targetElement=null;this.lastTouchIdentifier=this.touchStartY=this.touchStartX=0;this.touchBoundary=10;this.layer=a;if(!a||!a.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){return FastClick.prototype.onClick.apply(c,arguments)};this.onMouse=function(){return FastClick.prototype.onMouse.apply(c,arguments)};this.onTouchStart=function(){return FastClick.prototype.onTouchStart.apply(c,
10 | arguments)};this.onTouchEnd=function(){return FastClick.prototype.onTouchEnd.apply(c,arguments)};this.onTouchCancel=function(){return FastClick.prototype.onTouchCancel.apply(c,arguments)};FastClick.notNeeded(a)||(this.deviceIsAndroid&&(a.addEventListener("mouseover",this.onMouse,!0),a.addEventListener("mousedown",this.onMouse,!0),a.addEventListener("mouseup",this.onMouse,!0)),a.addEventListener("click",this.onClick,!0),a.addEventListener("touchstart",this.onTouchStart,!1),a.addEventListener("touchend",
11 | this.onTouchEnd,!1),a.addEventListener("touchcancel",this.onTouchCancel,!1),Event.prototype.stopImmediatePropagation||(a.removeEventListener=function(b,c,e){var f=Node.prototype.removeEventListener;"click"===b?f.call(a,b,c.hijacked||c,e):f.call(a,b,c,e)},a.addEventListener=function(b,c,e){var f=Node.prototype.addEventListener;"click"===b?f.call(a,b,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),e):f.call(a,b,c,e)}),"function"===typeof a.onclick&&(b=a.onclick,a.addEventListener("click",
12 | function(a){b(a)},!1),a.onclick=null))}FastClick.prototype.deviceIsAndroid=0c.offsetHeight){b=c;a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)};FastClick.prototype.getTargetElementFromEventTarget=function(a){return a.nodeType===Node.TEXT_NODE?a.parentNode:a};
17 | FastClick.prototype.onTouchStart=function(a){var b,c,d;if(1a.timeStamp-this.lastClickTime&&a.preventDefault();return!0};FastClick.prototype.touchHasMoved=function(a){a=a.changedTouches[0];var b=this.touchBoundary;return Math.abs(a.pageX-this.touchStartX)>b||Math.abs(a.pageY-this.touchStartY)>b?!0:!1};FastClick.prototype.findControl=function(a){return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")};
19 | FastClick.prototype.onTouchEnd=function(a){var b,c,d=this.targetElement;this.touchHasMoved(a)&&(this.trackingClick=!1,this.targetElement=null);if(!this.trackingClick)return!0;if(200>a.timeStamp-this.lastClickTime)return this.cancelNextClick=!0;this.lastClickTime=a.timeStamp;b=this.trackingClickStart;this.trackingClick=!1;this.trackingClickStart=0;this.deviceIsIOSWithBadTarget&&(c=a.changedTouches[0],d=document.elementFromPoint(c.pageX-window.pageXOffset,c.pageY-window.pageYOffset)||d,d.fastClickScrollParent=
20 | this.targetElement.fastClickScrollParent);c=d.tagName.toLowerCase();if("label"===c){if(b=this.findControl(d)){this.focus(d);if(this.deviceIsAndroid)return!1;d=b}}else if(this.needsFocus(d)){if(100
2 |
3 |
4 |
5 |
6 | Alarm Server
7 |
8 |
9 |
10 |
11 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
88 |
89 |
90 |
115 |
116 |
161 |
162 |
222 |
223 |
224 |
225 |
226 |
231 |
Alarm Server
232 |
233 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
--------------------------------------------------------------------------------
/Envisalink.py:
--------------------------------------------------------------------------------
1 | import asyncore, asynchat
2 | import socket
3 | import logging
4 | import time
5 | import datetime
6 |
7 | from envisalinkdefs import evl_ResponseTypes
8 | from envisalinkdefs import evl_Defaults
9 | from envisalinkdefs import evl_ArmModes
10 |
11 | ALARMSTATE={'version' : 0.2}
12 |
13 | def dict_merge(a, b):
14 | c = a.copy()
15 | c.update(b)
16 | return c
17 |
18 | def getMessageType(code):
19 | return evl_ResponseTypes[code]
20 |
21 | def to_chars(string):
22 | chars = []
23 | for char in string:
24 | chars.append(ord(char))
25 | return chars
26 |
27 | def get_checksum(code, data):
28 | return ("%02X" % sum(to_chars(code)+to_chars(data)))[-2:]
29 |
30 | class Client(asynchat.async_chat):
31 | def __init__(self, config, proxyclients):
32 |
33 | self.logger = logging.getLogger('alarmserver.EnvisalinkClient')
34 |
35 | self.logger.debug('Staring Envisalink Client')
36 | # Call parent class's __init__ method
37 | asynchat.async_chat.__init__(self)
38 |
39 | # save dict reference to connected clients
40 | self._proxyclients = proxyclients
41 |
42 | # alarm sate
43 | self._alarmstate = ALARMSTATE
44 |
45 | # Define some private instance variables
46 | self._buffer = []
47 |
48 | # Are we logged in?
49 | self._loggedin = False
50 |
51 | # Set our terminator to \n
52 | self.set_terminator("\r\n")
53 |
54 | # Set config
55 | self._config = config
56 |
57 | # Reconnect delay
58 | self._retrydelay = 10
59 |
60 | self.do_connect()
61 |
62 | def do_connect(self, reconnect = False):
63 | # Create the socket and connect to the server
64 | if reconnect == True:
65 | self.logger.warning('Connection failed, retrying in '+str(self._retrydelay)+ ' seconds')
66 | for i in range(0, self._retrydelay):
67 | time.sleep(1)
68 |
69 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
70 |
71 | self.logger.debug('Connecting to {}:{}'.format(self._config.ENVISALINKHOST, self._config.ENVISALINKPORT))
72 | self.connect((self._config.ENVISALINKHOST, self._config.ENVISALINKPORT))
73 |
74 | def collect_incoming_data(self, data):
75 | # Append incoming data to the buffer
76 | self._buffer.append(data)
77 |
78 | def found_terminator(self):
79 | line = "".join(self._buffer)
80 | self.handle_line(line)
81 | self._buffer = []
82 |
83 | def handle_connect(self):
84 | self.logger.info("Connected to %s:%i" % (self._config.ENVISALINKHOST, self._config.ENVISALINKPORT))
85 | pass
86 |
87 | def handle_close(self):
88 | self._loggedin = False
89 | self.close()
90 | self.logger.info("Disconnected from %s:%i" % (self._config.ENVISALINKHOST, self._config.ENVISALINKPORT))
91 | self.do_connect(True)
92 |
93 | def handle_error(self):
94 | self._loggedin = False
95 | self.close()
96 | self.logger.error("Disconnected from %s:%i" % (self._config.ENVISALINKHOST, self._config.ENVISALINKPORT))
97 | self.do_connect(True)
98 |
99 | def send_command(self, code, data, checksum = True):
100 | if checksum == True:
101 | to_send = code+data+get_checksum(code,data)+'\r\n'
102 | else:
103 | to_send = code+data+'\r\n'
104 |
105 | self.logger.debug('TX > '+to_send[:-1])
106 | self.push(to_send)
107 |
108 | def handle_line(self, input):
109 | if input != '':
110 | for client in self._proxyclients:
111 | self._proxyclients[client].send_command(input, False)
112 |
113 | code=int(input[:3])
114 | parameters=input[3:][:-2]
115 | event = getMessageType(int(code))
116 | message = self.format_event(event, parameters)
117 | self.logger.debug('RX < ' +str(code)+' - '+message)
118 |
119 | try:
120 | handler = "handle_%s" % evl_ResponseTypes[code]['handler']
121 | except KeyError:
122 | #call general event handler
123 | self.handle_event(code, parameters, event, message)
124 | return
125 |
126 | try:
127 | func = getattr(self, handler)
128 | except AttributeError:
129 | raise CodeError("Handler function doesn't exist")
130 |
131 | func(code, parameters, event, message)
132 |
133 | def format_event(self, event, parameters):
134 | if 'type' in event:
135 | if event['type'] in ('partition', 'zone'):
136 | if event['type'] == 'partition':
137 | # If parameters includes extra digits then this next line would fail
138 | # without looking at just the first digit which is the partition number
139 | if int(parameters[0]) in self._config.PARTITIONNAMES:
140 | # After partition number can be either a usercode
141 | # or for event 652 a type of arm mode (single digit)
142 | # Usercode is always 4 digits padded with zeros
143 | if len(str(parameters)) == 5:
144 | # We have a usercode
145 | try:
146 | usercode = int(parameters[1:5])
147 | except:
148 | usercode = 0
149 | if int(usercode) in self._config.ALARMUSERNAMES:
150 | alarmusername = self._config.ALARMUSERNAMES[int(usercode)]
151 | else:
152 | # Didn't find a username, use the code instead
153 | alarmusername = usercode
154 | return event['name'].format(str(self._config.PARTITIONNAMES[int(parameters[0])]), str(alarmusername))
155 | elif len(parameters) == 2:
156 | # We have an arm mode instead, get it's friendly name
157 | armmode = evl_ArmModes[int(parameters[1])]
158 | return event['name'].format(str(self._config.PARTITIONNAMES[int(parameters[0])]), str(armmode))
159 | else:
160 | return event['name'].format(str(self._config.PARTITIONNAMES[int(parameters)]))
161 | elif event['type'] == 'zone':
162 | if int(parameters) in self._config.ZONENAMES:
163 | if self._config.ZONENAMES[int(parameters)]!=False:
164 | return event['name'].format(str(self._config.ZONENAMES[int(parameters)]))
165 |
166 | return event['name'].format(str(parameters))
167 |
168 | #envisalink event handlers, some events are unhandeled.
169 | def handle_login(self, code, parameters, event, message):
170 | if parameters == '3':
171 | self._loggedin = True
172 | self.send_command('005', self._config.ENVISALINKPASS)
173 | if parameters == '1':
174 | self.send_command('001', '')
175 | if parameters == '0':
176 | self.logger.warning('Incorrect envisalink password')
177 | sys.exit(0)
178 |
179 | def handle_event(self, code, parameters, event, message):
180 | # only handle events with a 'type' defined
181 | if not 'type' in event:
182 | return
183 |
184 | if not event['type'] in self._alarmstate:
185 | self._alarmstate[event['type']]={'lastevents' : []}
186 |
187 | # save event in alarm state depending on
188 | # the type of event
189 |
190 | parameters = int(parameters)
191 |
192 | # if zone event
193 | if event['type'] == 'zone':
194 | zone = parameters
195 | # if the zone is named in the config file save info in self._alarmstate
196 | if zone in self._config.ZONENAMES:
197 | # save zone if not already there
198 | if not zone in self._alarmstate['zone']:
199 | self._alarmstate['zone'][zone] = {'name' : self._config.ZONENAMES[zone]}
200 | else:
201 | self.logger.debug('Ignoring unnamed zone {}'.format(zone))
202 |
203 | # if partition event
204 | elif event['type'] == 'partition':
205 | partition = parameters
206 | if partition in self._config.PARTITIONNAMES:
207 | # save partition name in alarmstate
208 | if not partition in self._alarmstate['partition']:
209 | self._alarmstate['partition'][partition] = {'name' : self._config.PARTITIONNAMES[partition]}
210 | else:
211 | self.logger.debug('Ignoring unnamed partition {}'.format(partition))
212 | else:
213 | if not parameters in self._alarmstate[event['type']]:
214 | self._alarmstate[event['type']][partition] = {}
215 |
216 | # shorthand to event state
217 | eventstate = self._alarmstate[event['type']]
218 |
219 | # return if the parameters isn't in the alarm event state
220 | # i.e. if the current event type is in zone 1 (event[type]:zone, param:1)
221 | # then, if there isn't an alaramstate['zone'][2], return
222 | if not parameters in eventstate:
223 | return
224 |
225 | # populate status with defaults if there isn't already a status
226 | if not 'status' in eventstate[parameters]:
227 | eventstate[parameters]['status'] = evl_Defaults[event['type']]
228 |
229 | prev_state = eventstate[parameters]['status']
230 | # save event status
231 | if 'status' in event:
232 | eventstate[parameters]['status']=dict_merge(eventstate[parameters]['status'], event['status'])
233 |
234 | # append event to lastevents, crete list if it doesn't exist
235 | if not 'lastevents' in eventstate[parameters]:
236 | eventstate[parameters]['lastevents'] = []
237 |
238 | # if the state of the alarm (i.e., zone, partition, etc) remains
239 | # unchanged after event['status'] has been merged, return and
240 | # do not store in history
241 | if prev_state == eventstate[parameters]['status']:
242 | self.logger.debug('Discarded event. State not changed. ({} {})'.format(event['type'], parameters))
243 | return
244 |
245 | # if lastevents is a list of non-zero length
246 | if eventstate[parameters]['lastevents']:
247 | # if this event is the same as previous discard it
248 | # except if lastevents is empty, then we get an IndexError exception
249 | if eventstate[parameters]['lastevents'][-1]['code'] == code:
250 | self.logger.debug('{}:{} ({}) discarded duplicate event'.format(event['type'], parameters, code))
251 | return
252 |
253 | # append this event to lastevents
254 | eventstate[parameters]['lastevents'].append({
255 | 'datetime' : str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
256 | 'code' : code,
257 | 'message' : message})
258 |
259 | # manage last events list if it's > MAXEVENTS
260 | if len(eventstate[parameters]['lastevents']) > self._config.MAXEVENTS:
261 | eventstate[parameters]['lastevents'].pop(0)
262 |
263 |
264 | eventstate['lastevents'].append({'datetime' : str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), 'message' : message})
265 | if len(eventstate['lastevents']) > self._config.MAXALLEVENTS:
266 | eventstate['lastevents'].pop(0)
267 |
268 |
269 | def handle_zone(self, code, parameters, event, message):
270 | self.handle_event(code, parameters[1:], event, message)
271 |
272 | def handle_partition(self, code, parameters, event, message):
273 | self.handle_event(code, parameters[0], event, message)
274 |
275 | class Proxy(asyncore.dispatcher):
276 | def __init__(self, config, server):
277 |
278 | self.logger = logging.getLogger('alarmserver.Proxy')
279 |
280 | self._config = config
281 | if self._config.ENABLEPROXY == False:
282 | return
283 |
284 | asyncore.dispatcher.__init__(self)
285 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
286 | self.set_reuse_addr()
287 | self.logger.info('Envisalink Proxy Started')
288 |
289 | self.bind(("", self._config.ENVISALINKPROXYPORT))
290 | self.listen(5)
291 |
292 | def handle_accept(self):
293 | pair = self.accept()
294 | if pair is None:
295 | pass
296 | else:
297 | sock, addr = pair
298 | self.logger.info('Incoming proxy connection from %s' % repr(addr))
299 | handler = ProxyChannel(server, self._config.ENVISALINKPROXYPASS, sock, addr)
300 |
301 |
--------------------------------------------------------------------------------
/envisalinkdefs.py:
--------------------------------------------------------------------------------
1 | ## Alarm Server
2 | ## Supporting Envisalink 2DS/3
3 | ## Written by donnyk+envisalink@gmail.com
4 | ##
5 | ## This code is under the terms of the GPL v3 license.
6 |
7 | evl_Defaults = {
8 | 'zone' : {'open' : False, 'fault' : False, 'alarm' : False, 'tamper' : False},
9 | 'partition' : {'ready' : False, 'trouble' : False, 'exit_delay' : False, 'entry_delay' : False, 'armed' : False, 'armed_bypass' : False, 'alarm' : False, 'tamper' : False, 'chime' : False, 'trouble_led' : False},
10 | 'system' : {'fire_key_alarm' : False, 'aux_key_alarm' : False, 'panic_key_alarm' : False, '2wire_alarm' : False, 'battery_trouble' : False, 'ac_trouble' : False, 'system_bell_trouble' : False, 'system_tamper' : False, 'fire_trouble' : False}
11 | }
12 |
13 | evl_ArmModes = {
14 | 0 : 'Away',
15 | 1 : 'Stay',
16 | 2 : 'Zero Entry Away',
17 | 3 : 'Zero Entry Stay'
18 | }
19 |
20 | evl_ResponseTypes = {
21 | 500 : {'name' : 'Command Acknowledge', 'description' : 'A command has been received successfully.'},
22 | 501 : {'name' : 'Command Error', 'description' : 'A command has been received with a bad checksum.'},
23 | 502 : {'name' : 'System Error {0}', 'description' : 'An error has been detected.'},
24 | 505 : {'name' : 'Login Interaction', 'description' : 'Sent During Session Login Only.', 'handler' : 'login'},
25 | 510 : {'name' : 'Keypad Led State - Partition 1', 'description' : 'Outputted when the TPI has deceted a change of state in the Partition 1 keypad LEDs.'},
26 | 511 : {'name' : 'Keypad Led Flash State - Partition 1', 'description' : 'Outputed when the TPI has detected a change of state in the Partition 1 keypad LEDs as to whether to flash or not. Overrides 510. That is, if 511 says the PROGRAM LED is flashing, then it doesn''t matter what 510 says.'},
27 | 550 : {'name' : 'Time/Date Broadcast', 'description' : 'Outputs the current security system time.'},
28 | 560 : {'name' : 'Ring Detected', 'description' : 'The Panel has detected a ring on the telephone line. Note: This command will only be issued if an ESCORT 5580xx module is present.'},
29 | 561 : {'name' : 'Indoor Temperature Broadcast', 'description' : 'If an ESCORT 5580TC is installed, and at least one ENERSTAT thermostat, this command displays the interior temperature and the thermostat number.'},
30 | 562 : {'name' : 'Outdoor Temperature Broadcast', 'description' : 'If an ESCORT 5580TC is installed, and at least one ENERSTAT thermostat, this command displays the exterior temperature and the thermostat number.'},
31 | 601 : {'type' : 'zone', 'name' : 'Partition {0[0]} Zone {0[1]}{0[2]}{0[3]} Alarm', 'description' : 'A zone has gone into alarm.', 'handler' : 'zone', 'status' : {'alarm' : True}},
32 | 602 : {'type' : 'zone', 'name' : 'Partition {0[0]} Zone {0[1]}{0[2]}{0[3]} Alarm Restore', 'description' : 'A zone alarm has been restored.', 'handler' : 'zone', 'status' : {'alarm' : False}},
33 | 603 : {'type' : 'zone', 'name' : 'Partition {0[0]} Zone {0[1]}{0[2]}{0[3]} Tamper', 'description' : 'A zone has a tamper condition.', 'handler' : 'zone', 'status' : {'tamper' : True}},
34 | 604 : {'type' : 'zone', 'name' : 'Partition {0[0]} Zone {0[1]}{0[2]}{0[3]} Tamper Restore', 'description' : 'A zone tamper condition has been restored.', 'handler' : 'zone', 'status' : {'tamper' : False}},
35 | 605 : {'type' : 'zone', 'name' : 'Zone {0} Fault', 'description' : 'A zone has a fault condition.', 'status' : {'fault' : True}},
36 | 606 : {'type' : 'zone', 'name' : 'Zone {0} Fault Restore', 'description' : 'A zone fault condition has been restored.', 'status' : {'fault' : False}},
37 | 609 : {'type' : 'zone', 'name' : 'Zone {0} Open', 'description' : 'General status of the zone.', 'status' : {'open' : True}},
38 | 610 : {'type' : 'zone', 'name' : 'Zone {0} Restored', 'description' : 'General status of the zone.', 'status' : {'open' : False}},
39 | 615 : {'name' : 'Envisalink Zone Timer Dump', 'description' : 'This command contains the raw zone timers used inside the Envisalink. The dump is a 256 character packed HEX string representing 64 UINT16 (little endian) zone timers. Zone timers count down from 0xFFFF (zone is open) to 0x0000 (zone is closed too long ago to remember). Each ''tick'' of the zone time is actually 5 seconds so a zone timer of 0xFFFE means ''5 seconds ago''. Remember, the zone timers are LITTLE ENDIAN so the above example would be transmitted as FEFF.'},
40 | 620 : {'name' : 'Duress Alarm', 'description' : 'A duress code has been entered on a system keypad.'},
41 | 621 : {'type' : 'system', 'name' : '[F] Key Alarm', 'description' : 'A Fire key alarm has been detected.', 'status' : {'fire_key_alarm' : True}},
42 | 622 : {'type' : 'system', 'name' : '[F] Key Alarm', 'description' : 'A Fire key alarm has been restored (sent automatically).', 'status' : {'fire_key_alarm' : False}},
43 | 623 : {'type' : 'system', 'name' : '[A] Key Alarm', 'description' : 'A Auxillary key alarm has been detected.', 'status' : {'aux_key_alarm' : True}},
44 | 624 : {'type' : 'system', 'name' : '[A] Key Alarm', 'description' : 'A Auxillary key alarm has been restored (sent automatically).', 'status' : {'aux_key_alarm' : False}},
45 | 625 : {'type' : 'system', 'name' : '[P] Key Alarm', 'description' : 'A Panic key alarm has been detected.', 'status' : {'panic_key_alarm' : True}},
46 | 626 : {'type' : 'system', 'name' : '[P] Key Alarm', 'description' : 'A Panic key alarm has been restored (sent automatically).', 'status' : {'panic_key_alarm' : False}},
47 | 631 : {'type' : 'system', 'name' : '2-Wire Smoke/Aux Alarm', 'description' : 'A 2-wire smoke/Auxiliary alarm has been activated.', 'status' : {'2wire_alarm' : True}},
48 | 632 : {'type' : 'system', 'name' : '2-Wire Smoke/Aux Restore', 'description' : 'A 2-wire smoke/Auxiliary alarm has been restored.', 'status' : {'2wire_alarm' : False}},
49 | 650 : {'type' : 'partition', 'name' : 'Partition {0} Ready', 'description' : 'Partition can now be armed (all zones restored, no troubles, etc). Also issued at the end of Bell Timeout if the partition was READY when an alarm occurred.', 'status' : {'ready' : True, 'pgm_output' : False}},
50 | 651 : {'type' : 'partition', 'name' : 'Partition {0} Not Ready', 'description' : 'Partition cannot be armed (zones open, trouble present, etc).', 'status' : {'ready' : False}},
51 | 652 : {'type' : 'partition', 'name' : 'Partition {0} Armed Mode {1}', 'description' : 'Partition has been armed - sent at the end of exit delay Also sent after an alarm if the Bell Cutoff Timer expires Mode is appended to indicate whether the partition is armed AWAY, STAY, ZERO-ENTRY-AWAY, or ZERO-ENTRY-STAY.', 'handler' : 'partition', 'status' : {'armed' : True, 'exit_delay' : False}},
52 | 653 : {'type' : 'partition', 'name' : 'Partition {0} Ready - Force Arming Enabled', 'description' : 'Partition can now be armed (all zones restored, no troubles, etc). Also issued at the end of Bell Timeout if the partition was READY when an alarm occurred.', 'status' : {'ready' : True}},
53 | 654 : {'type' : 'partition', 'name' : 'Partition {0} In Alarm', 'description' : 'A partition is in alarm.', 'status' : {'alarm' : True}},
54 | 655 : {'type' : 'partition', 'name' : 'Partition {0} Disarmed', 'description' : 'A partition has been disarmed.', 'status' : {'alarm' : False, 'armed' : False, 'exit_delay' : False, 'entry_delay' : False}},
55 | 656 : {'type' : 'partition', 'name' : 'Partition {0} Exit Delay in Progress', 'description' : 'A partition is in Exit Delay.', 'status' : {'exit_delay' : True}},
56 | 657 : {'type' : 'partition', 'name' : 'Partition {0} Entry Delay in Progress', 'description' : 'A partition is in Entry Delay.', 'status' : {'entry_delay' : True}},
57 | 658 : {'type' : 'partition', 'name' : 'Partition {0} Keypad Lock-out', 'description' : 'A partition is in Keypad Lockout due to too many failed user code attempts.'},
58 | 659 : {'type' : 'partition', 'name' : 'Partition {0} Failed to Arm', 'description' : 'An attempt to arm the partition has failed.'},
59 | 660 : {'type' : 'partition', 'name' : 'Partition {0} PGM Output is in Progress', 'description' : '*71, *72, *73, or *74 has been pressed.', 'status': {'pgm_output' : True}},
60 | 663 : {'type' : 'partition', 'name' : 'Partition {0} Chime Enabled', 'description' : 'The door chime feature has been enabled.', 'status' : {'chime' : True}},
61 | 664 : {'type' : 'partition', 'name' : 'Partition {0} Chime Disabled', 'description' : 'The door chime feature has been disabled.', 'status' : {'chime' : False}},
62 | 670 : {'type' : 'partition', 'name' : 'Partition {0} Invalid Access Code', 'description' : 'Invalid Access Code.'},
63 | 671 : {'type' : 'partition', 'name' : 'Partition {0} Function Not Available', 'description' : 'A partition is in Entry delay.'},
64 | 672 : {'type' : 'partition', 'name' : 'Partition {0} Failure to Arm', 'description' : 'An attempt was made to arm the partition and it failed.'},
65 | 673 : {'type' : 'partition', 'name' : 'Partition {0} is Busy', 'description' : 'The partition is busy (another keypad is programming or an installer is programming).'},
66 | 674 : {'type' : 'partition', 'name' : 'Partition {0} System Arming in Progress', 'description' : 'This system is auto-arming and is in arm warning delay.'},
67 | 680 : {'name' : 'System in installers mode', 'description' : 'System has entered installers mode'},
68 | 700 : {'type' : 'partition', 'name' : 'Partition {0} User {1} Closing', 'description' : 'A partition has been armed by a user - sent at the end of exit delay.', 'handler' : 'partition', 'status' : {'armed' : True, 'exit_delay' : False}},
69 | 701 : {'type' : 'partition', 'name' : 'Partition {0} Special Closing', 'description' : 'A partition has been armed by one of the following methods: Quick Arm, Auto Arm, Keyswitch, DLS software, Wireless Key.', 'status' : {'armed' : True, 'exit_delay' : False}},
70 | 702 : {'type' : 'partition', 'name' : 'Partition {0} Partial Closing', 'description' : 'A partition has been armed but one or more zones have been bypassed.', 'status' : {'armed' : True, 'exit_delay' : False}},
71 | 750 : {'type' : 'partition', 'name' : 'Partition {0} User {1} Opening', 'description' : 'A partition has been disarmed by a user.', 'handler' : 'partition', 'status' : {'armed' : False, 'entry_delay' : False}},
72 | 751 : {'type' : 'partition', 'name' : 'Partition {0} Special Opening', 'description' : 'A partition has been disarmed by one of the following methods: Keyswitch, DLS software, Wireless Key.', 'status' : {'armed' : False, 'entry_delay' : False}},
73 | 800 : {'type' : 'system', 'name' : 'Panel Battery Trouble', 'description' : 'The panel has a low battery.', 'status' : {'battery_trouble' : True}},
74 | 801 : {'type' : 'system', 'name' : 'Panel Battery Trouble Restore', 'description' : 'The panel''s low battery has been restored.', 'status' : {'battery_trouble' : False}},
75 | 802 : {'type' : 'system', 'name' : 'Panel AC Trouble', 'description' : 'AC power to the panel has been removed.', 'status' : {'ac_trouble' : True}},
76 | 803 : {'type' : 'system', 'name' : 'Panel AC Restore', 'description' : 'AC power to the panel has been restored.', 'status' : {'ac_trouble' : False}},
77 | 806 : {'type' : 'system', 'name' : 'System Bell Trouble', 'description' : 'An open circuit has been detected across the bell terminals.', 'status' : {'system_bell_trouble' : True}},
78 | 807 : {'type' : 'system', 'name' : 'System Bell Trouble Restoral', 'description' : 'The bell trouble has been restored.', 'status' : {'system_bell_trouble' : False}},
79 | 814 : {'name' : 'FTC Trouble', 'description' : 'The panel has failed to communicate successfully to the monitoring.'},
80 | 816 : {'name' : 'Buffer Near Full', 'description' : 'Sent when the panel''s Event Buffer is 75% full from when it was last uploaded to DLS.'},
81 | 829 : {'type' : 'system', 'name' : 'General System Tamper', 'description' : 'A tamper has occurred with one of the following modules: Zone Expander, PC5132, PC5204, PC5208, PC5400, PC59XX, LINKS 2X50, PC5108L, PC5100, PC5200.', 'status' : {'system_tamper' : True}},
82 | 830 : {'type' : 'system', 'name' : 'General System Tamper Restore', 'description' : 'A general system Tamper has been restored.', 'status' : {'system_tamper' : False}},
83 | 840 : {'type' : 'partition', 'name' : 'Partition {0} Trouble LED ON', 'description' : 'This command shows the general trouble status that the trouble LED on a keypad normally shows. When ON, it means there is a trouble on this partition. This command when the LED transitions from OFF, to ON.', 'status' : {'trouble' : True}},
84 | 841 : {'type' : 'partition', 'name' : 'Partition {0} Trouble LED OFF', 'description' : 'This command shows the general trouble status that the trouble LED on a keypad normally shows. When the LED is OFF, this usually means there are no troubles present on this partition but certain modes will blank this LED even in the presence of a partition trouble. This command when the LED transitions from ON, to OFF.', 'status' : {'trouble' : False}},
85 | 842 : {'type' : 'system', 'name' : 'Fire Trouble Alarm', 'description' : 'Fire Trouble Alarm', 'status' : {'fire_trouble' : True}},
86 | 843 : {'type' : 'system', 'name' : 'Fire Trouble Alarm Restore', 'description' : 'Fire Trouble Alarm Restore', 'status' : {'fire_trouble' : False}},
87 | 849 : {'name' : 'Verbose Trouble Status', 'description' : 'This command is issued when a trouble appears on the system and roughly every 5 minutes until the trouble is cleared. The two characters are a bitfield (similar to 510,511). The meaning of each bit is the same as what you see on an LED keypad (see the user manual).'},
88 | 900 : {'name' : 'Code Required', 'description' : 'This command will tell the API to enter an access code. Once entered, the 200 command will be sent to perform the required action. The code should be entered within the window time of the panel.'},
89 | 912 : {'name' : 'Command Output Pressed', 'description' : 'This command will tell the API to enter an access code. Once entered, the 200 command will be sent to perform the required action. The code should be entered within the window time of the panel.'},
90 | 921 : {'name' : 'Master Code Required', 'description' : 'This command will tell the API to enter a master access code. Once entered, the 200 command will be sent to perform the required action. The code should be entered within the window time of the panel.'},
91 | 922 : {'name' : 'Installers Code Required', 'description' : 'This command will tell the API to enter an installers access code. Once entered, the 200 command will be sent to perform the required action. The code should be entered within the window time of the panel.'},
92 | }
93 |
--------------------------------------------------------------------------------
/alarmserver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | ## Alarm Server
3 | ## Supporting Envisalink 2DS/3
4 | ## Contributors: https://github.com/juggie/AlarmServer/graphs/contributors
5 | ## Compatibility: https://github.com/juggie/AlarmServer/wiki/Compatibility
6 | ##
7 | ## This code is under the terms of the GPL v3 license.
8 |
9 |
10 | import asyncore, asynchat
11 | import ConfigParser
12 | import datetime
13 | import os, socket, string, sys, httplib, urllib, urlparse, ssl
14 | import json
15 | import hashlib
16 | import time
17 | import getopt
18 | import logging
19 |
20 | logger = logging.getLogger('alarmserver')
21 | logger.setLevel(logging.DEBUG)
22 |
23 | # Console handler
24 | # Prints all messages (debug level)
25 | ch = logging.StreamHandler();
26 | ch.setLevel(logging.DEBUG)
27 | # create formatter
28 | formatter = logging.Formatter(
29 | fmt='%(asctime)s %(name)s %(levelname)s: %(message)s',
30 | datefmt='%b %d %H:%M:%S')
31 | ch.setFormatter(formatter);
32 | # add handlers to logger
33 | logger.addHandler(ch)
34 |
35 | import HTTPChannel
36 | import Envisalink
37 |
38 | LOGTOFILE = False
39 |
40 | class CodeError(Exception): pass
41 |
42 | MAXPARTITIONS=16
43 | MAXZONES=128
44 | MAXALARMUSERS=47
45 | CONNECTEDCLIENTS={}
46 |
47 | def to_chars(string):
48 | chars = []
49 | for char in string:
50 | chars.append(ord(char))
51 | return chars
52 |
53 | def get_checksum(code, data):
54 | return ("%02X" % sum(to_chars(code)+to_chars(data)))[-2:]
55 |
56 | #currently supports pushover notifications, more to be added
57 | #including email, text, etc.
58 | #to be fixed!
59 | def send_notification(config, message):
60 | if config.PUSHOVER_ENABLE == True:
61 | conn = httplib.HTTPSConnection("api.pushover.net:443")
62 | conn.request("POST", "/1/messages.json",
63 | urllib.urlencode({
64 | "token": "qo0nwMNdX56KJl0Avd4NHE2onO4Xff",
65 | "user": config.PUSHOVER_USERTOKEN,
66 | "message": str(message),
67 | }), { "Content-type": "application/x-www-form-urlencoded" })
68 |
69 | class AlarmServerConfig():
70 | def __init__(self, configfile):
71 |
72 | self._config = ConfigParser.ConfigParser()
73 | self._config.read(configfile)
74 |
75 | self.LOGURLREQUESTS = self.read_config_var('alarmserver', 'logurlrequests', True, 'bool')
76 | self.HTTPSPORT = self.read_config_var('alarmserver', 'httpsport', 8111, 'int')
77 | self.CERTFILE = self.read_config_var('alarmserver', 'certfile', 'server.crt', 'str')
78 | self.KEYFILE = self.read_config_var('alarmserver', 'keyfile', 'server.key', 'str')
79 | self.MAXEVENTS = self.read_config_var('alarmserver', 'maxevents', 10, 'int')
80 | self.MAXALLEVENTS = self.read_config_var('alarmserver', 'maxallevents', 100, 'int')
81 | self.ENVISALINKHOST = self.read_config_var('envisalink', 'host', 'envisalink', 'str')
82 | self.ENVISALINKPORT = self.read_config_var('envisalink', 'port', 4025, 'int')
83 | self.ENVISALINKPASS = self.read_config_var('envisalink', 'pass', 'user', 'str')
84 | self.ENABLEPROXY = self.read_config_var('envisalink', 'enableproxy', True, 'bool')
85 | self.ENVISALINKPROXYPORT = self.read_config_var('envisalink', 'proxyport', self.ENVISALINKPORT, 'int')
86 | self.ENVISALINKPROXYPASS = self.read_config_var('envisalink', 'proxypass', self.ENVISALINKPASS, 'str')
87 | self.PUSHOVER_ENABLE = self.read_config_var('pushover', 'enable', False, 'bool')
88 | self.PUSHOVER_USERTOKEN = self.read_config_var('pushover', 'enable', False, 'bool')
89 | self.ALARMCODE = self.read_config_var('envisalink', 'alarmcode', 1111, 'int')
90 | self.EVENTTIMEAGO = self.read_config_var('alarmserver', 'eventtimeago', True, 'bool')
91 | self.LOGFILE = self.read_config_var('alarmserver', 'logfile', '', 'str')
92 | global LOGTOFILE
93 | if self.LOGFILE == '':
94 | LOGTOFILE = False
95 | else:
96 | LOGTOFILE = True
97 |
98 | self.PARTITIONNAMES={}
99 | for i in range(1, MAXPARTITIONS+1):
100 | partition = self.read_config_var('alarmserver', 'partition'+str(i), False, 'str', True)
101 | if partition: self.PARTITIONNAMES[i] = partition
102 |
103 | self.ZONENAMES={}
104 | for i in range(1, MAXZONES+1):
105 | zone = self.read_config_var('alarmserver', 'zone'+str(i), False, 'str', True)
106 | if zone: self.ZONENAMES[i] = zone
107 |
108 | self.ALARMUSERNAMES={}
109 | for i in range(1, MAXALARMUSERS+1):
110 | user = self.read_config_var('alarmserver', 'user'+str(i), False, 'str', True)
111 | if user: self.ALARMUSERNAMES[i] = user
112 |
113 | if self.PUSHOVER_USERTOKEN == False and self.PUSHOVER_ENABLE == True: self.PUSHOVER_ENABLE = False
114 |
115 | def defaulting(self, section, variable, default, quiet = False):
116 | if quiet == False:
117 | print('Config option '+ str(variable) + ' not set in ['+str(section)+'] defaulting to: \''+str(default)+'\'')
118 |
119 | def read_config_var(self, section, variable, default, type = 'str', quiet = False):
120 | try:
121 | if type == 'str':
122 | return self._config.get(section,variable)
123 | elif type == 'bool':
124 | return self._config.getboolean(section,variable)
125 | elif type == 'int':
126 | return int(self._config.get(section,variable))
127 | except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
128 | self.defaulting(section, variable, default, quiet)
129 | return default
130 |
131 | class AlarmServer(asyncore.dispatcher):
132 | def __init__(self, config):
133 | # Call parent class's __init__ method
134 | asyncore.dispatcher.__init__(self)
135 |
136 | # Create Envisalink client object
137 | self._envisalinkclient = Envisalink.Client(config, CONNECTEDCLIENTS)
138 |
139 | #Store config
140 | self._config = config
141 |
142 | logger.info('AlarmServer on HTTPS port {}'.format(config.HTTPSPORT))
143 |
144 | # Create socket and listen on it
145 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
146 | self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
147 | self.bind(("", config.HTTPSPORT))
148 | self.listen(5)
149 |
150 | def handle_accept(self):
151 | # Accept the connection
152 | conn, addr = self.accept()
153 | if (config.LOGURLREQUESTS):
154 | logger.debug('Incoming web connection from %s' % repr(addr))
155 |
156 | try:
157 | HTTPChannel.HTTPChannel(self, ssl.wrap_socket(conn, server_side=True, certfile=config.CERTFILE, keyfile=config.KEYFILE, ssl_version=ssl.PROTOCOL_TLSv1), addr)
158 | except ssl.SSLError:
159 | logger.warning('Failed https connection, attempted with http')
160 | return
161 |
162 | def handle_request(self, channel, method, request, header):
163 | if (config.LOGURLREQUESTS):
164 | logger.debug('Web request: '+str(method)+' '+str(request))
165 |
166 | query = urlparse.urlparse(request)
167 | query_array = urlparse.parse_qs(query.query, True)
168 |
169 | if query.path == '/':
170 | channel.pushfile('index.html');
171 | elif query.path == '/api':
172 | channel.pushok(json.dumps(self._envisalinkclient._alarmstate))
173 | elif query.path == '/api/alarm/arm':
174 | channel.pushok(json.dumps({'response' : 'Request to arm received'}))
175 | self._envisalinkclient.send_command('030', '1')
176 | elif query.path == '/api/alarm/stayarm':
177 | channel.pushok(json.dumps({'response' : 'Request to arm in stay received'}))
178 | self._envisalinkclient.send_command('031', '1')
179 | elif query.path == '/api/alarm/armwithcode':
180 | channel.pushok(json.dumps({'response' : 'Request to arm with code received'}))
181 | self._envisalinkclient.send_command('033', '1' + str(query_array['alarmcode'][0]))
182 | elif query.path == '/api/pgm':
183 | channel.pushok(json.dumps({'response' : 'Request to trigger PGM'}))
184 | #self._envisalinkclient.send_command('020', '1' + str(query_array['pgmnum'][0]))
185 | self._envisalinkclient.send_command('071', '1' + "*7" + str(query_array['pgmnum'][0]))
186 | time.sleep(1)
187 | self._envisalinkclient.send_command('071', '1' + str(query_array['alarmcode'][0]))
188 | elif query.path == '/api/alarm/disarm':
189 | channel.pushok(json.dumps({'response' : 'Request to disarm received'}))
190 | if 'alarmcode' in query_array:
191 | self._envisalinkclient.send_command('040', '1' + str(query_array['alarmcode'][0]))
192 | else:
193 | self._envisalinkclient.send_command('040', '1' + str(self._config.ALARMCODE))
194 | elif query.path == '/api/refresh':
195 | channel.pushok(json.dumps({'response' : 'Request to refresh data received'}))
196 | self._envisalinkclient.send_command('001', '')
197 | elif query.path == '/api/config/eventtimeago':
198 | channel.pushok(json.dumps({'eventtimeago' : str(self._config.EVENTTIMEAGO)}))
199 | elif query.path == '/img/glyphicons-halflings.png':
200 | channel.pushfile('glyphicons-halflings.png')
201 | elif query.path == '/img/glyphicons-halflings-white.png':
202 | channel.pushfile('glyphicons-halflings-white.png')
203 | elif query.path == '/favicon.ico':
204 | channel.pushfile('favicon.ico')
205 | else:
206 | if len(query.path.split('/')) == 2:
207 | try:
208 | with open(sys.path[0] + os.sep + 'ext' + os.sep + query.path.split('/')[1]) as f:
209 | f.close()
210 | channel.pushfile(query.path.split('/')[1])
211 | except IOError as e:
212 | print "I/O error({0}): {1}".format(e.errno, e.strerror)
213 | channel.pushstatus(404, "Not found")
214 | channel.push("Content-type: text/html\r\n")
215 | channel.push("File not found")
216 | channel.push("\r\n")
217 | else:
218 | if (config.LOGURLREQUESTS):
219 | logger.warning("Invalid file requested")
220 |
221 | channel.pushstatus(404, "Not found")
222 | channel.push("Content-type: text/html\r\n")
223 | channel.push("\r\n")
224 |
225 | def handle_error(self):
226 | logger.exception('AlarmServer exception')
227 |
228 | class ProxyChannel(asynchat.async_chat):
229 | def __init__(self, server, proxypass, sock, addr):
230 | asynchat.async_chat.__init__(self, sock)
231 | self.server = server
232 | self.set_terminator("\r\n")
233 | self._buffer = []
234 | self._server = server
235 | self._clientMD5 = hashlib.md5(str(addr)).hexdigest()
236 | self._straddr = str(addr)
237 | self._proxypass = proxypass
238 | self._authenticated = False
239 |
240 | self.send_command('5053')
241 |
242 | def collect_incoming_data(self, data):
243 | # Append incoming data to the buffer
244 | self._buffer.append(data)
245 |
246 | def found_terminator(self):
247 | line = "".join(self._buffer)
248 | self._buffer = []
249 | self.handle_line(line)
250 |
251 | def handle_line(self, line):
252 | logger.info('PROXY REQ < '+line)
253 | if self._authenticated == True:
254 | self._server._envisalinkclient.send_command(line, '', False)
255 | else:
256 | self.send_command('500005')
257 | expectedstring = '005' + self._proxypass + get_checksum('005', self._proxypass)
258 | if line == ('005' + self._proxypass + get_checksum('005', self._proxypass)):
259 | logger.info('Proxy User Authenticated')
260 | CONNECTEDCLIENTS[self._straddr]=self
261 | self._authenticated = True
262 | self.send_command('5051')
263 | else:
264 | logger.warning('Proxy User Authentication failed')
265 | self.send_command('5050')
266 | self.close()
267 |
268 | def send_command(self, data, checksum = True):
269 | if checksum == True:
270 | to_send = data+get_checksum(data, '')+'\r\n'
271 | else:
272 | to_send = data+'\r\n'
273 |
274 | self.push(to_send)
275 |
276 | def handle_close(self):
277 | logger.info('Proxy connection from %s closed' % self._straddr)
278 | if self._straddr in CONNECTEDCLIENTS: del CONNECTEDCLIENTS[self._straddr]
279 | self.close()
280 |
281 | def handle_error(self):
282 | logger.exception('Proxy connection from %s errored' % self._straddr)
283 | if self._straddr in CONNECTEDCLIENTS: del CONNECTEDCLIENTS[self._straddr]
284 | self.close()
285 |
286 | def usage():
287 | print 'Usage: '+sys.argv[0]+' -c '
288 |
289 | def main(argv):
290 | try:
291 | opts, args = getopt.getopt(argv, "hc:", ["help", "config="])
292 | except getopt.GetoptError:
293 | usage()
294 | sys.exit(2)
295 | for opt, arg in opts:
296 | if opt in ("-h", "--help"):
297 | usage()
298 | sys.exit()
299 | elif opt in ("-c", "--config"):
300 | global conffile
301 | conffile = arg
302 |
303 |
304 | if __name__=="__main__":
305 | conffile='alarmserver.cfg'
306 | main(sys.argv[1:])
307 | logger.info('Using configuration file %s' % conffile)
308 | config = AlarmServerConfig(conffile)
309 | if LOGTOFILE:
310 | # File handler. Only print INFO and above
311 | fh = logging.FileHandler(config.LOGFILE)
312 | fh.setLevel(logging.INFO)
313 | fh.setFormatter(formatter)
314 | logger.addHandler(fh)
315 |
316 | logger.info('-'*30)
317 | logger.info('Alarm Server Starting')
318 |
319 | server = AlarmServer(config)
320 | proxy = Envisalink.Proxy(config, server)
321 |
322 | try:
323 | while True:
324 | asyncore.loop(timeout=2, count=1)
325 | # insert scheduling code here.
326 | except KeyboardInterrupt:
327 | print "Crtl+C pressed. Shutting down."
328 | logger.info('Shutting down from Ctrl+C')
329 |
330 | server.shutdown(socket.SHUT_RDWR)
331 | server.close()
332 | sys.exit()
333 |
--------------------------------------------------------------------------------
/ext/bootstrap-responsive.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.3.2
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
10 |
--------------------------------------------------------------------------------
/ext/moment.min.js:
--------------------------------------------------------------------------------
1 | // moment.js
2 | // version : 2.1.0
3 | // author : Tim Wood
4 | // license : MIT
5 | // momentjs.com
6 | !function(t){function e(t,e){return function(n){return u(t.call(this,n),e)}}function n(t,e){return function(n){return this.lang().ordinal(t.call(this,n),e)}}function s(){}function i(t){a(this,t)}function r(t){var e=t.years||t.year||t.y||0,n=t.months||t.month||t.M||0,s=t.weeks||t.week||t.w||0,i=t.days||t.day||t.d||0,r=t.hours||t.hour||t.h||0,a=t.minutes||t.minute||t.m||0,o=t.seconds||t.second||t.s||0,u=t.milliseconds||t.millisecond||t.ms||0;this._input=t,this._milliseconds=u+1e3*o+6e4*a+36e5*r,this._days=i+7*s,this._months=n+12*e,this._data={},this._bubble()}function a(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function o(t){return 0>t?Math.ceil(t):Math.floor(t)}function u(t,e){for(var n=t+"";n.lengthn;n++)~~t[n]!==~~e[n]&&r++;return r+i}function f(t){return t?ie[t]||t.toLowerCase().replace(/(.)s$/,"$1"):t}function l(t,e){return e.abbr=t,x[t]||(x[t]=new s),x[t].set(e),x[t]}function _(t){if(!t)return H.fn._lang;if(!x[t]&&A)try{require("./lang/"+t)}catch(e){return H.fn._lang}return x[t]}function m(t){return t.match(/\[.*\]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function y(t){var e,n,s=t.match(E);for(e=0,n=s.length;n>e;e++)s[e]=ue[s[e]]?ue[s[e]]:m(s[e]);return function(i){var r="";for(e=0;n>e;e++)r+=s[e]instanceof Function?s[e].call(i,t):s[e];return r}}function M(t,e){function n(e){return t.lang().longDateFormat(e)||e}for(var s=5;s--&&N.test(e);)e=e.replace(N,n);return re[e]||(re[e]=y(e)),re[e](t)}function g(t,e){switch(t){case"DDDD":return V;case"YYYY":return X;case"YYYYY":return $;case"S":case"SS":case"SSS":case"DDD":return I;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return R;case"a":case"A":return _(e._l)._meridiemParse;case"X":return B;case"Z":case"ZZ":return j;case"T":return q;case"MM":case"DD":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return J;default:return new RegExp(t.replace("\\",""))}}function p(t){var e=(j.exec(t)||[])[0],n=(e+"").match(ee)||["-",0,0],s=+(60*n[1])+~~n[2];return"+"===n[0]?-s:s}function D(t,e,n){var s,i=n._a;switch(t){case"M":case"MM":i[1]=null==e?0:~~e-1;break;case"MMM":case"MMMM":s=_(n._l).monthsParse(e),null!=s?i[1]=s:n._isValid=!1;break;case"D":case"DD":case"DDD":case"DDDD":null!=e&&(i[2]=~~e);break;case"YY":i[0]=~~e+(~~e>68?1900:2e3);break;case"YYYY":case"YYYYY":i[0]=~~e;break;case"a":case"A":n._isPm=_(n._l).isPM(e);break;case"H":case"HH":case"h":case"hh":i[3]=~~e;break;case"m":case"mm":i[4]=~~e;break;case"s":case"ss":i[5]=~~e;break;case"S":case"SS":case"SSS":i[6]=~~(1e3*("0."+e));break;case"X":n._d=new Date(1e3*parseFloat(e));break;case"Z":case"ZZ":n._useUTC=!0,n._tzm=p(e)}null==e&&(n._isValid=!1)}function Y(t){var e,n,s=[];if(!t._d){for(e=0;7>e;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];s[3]+=~~((t._tzm||0)/60),s[4]+=~~((t._tzm||0)%60),n=new Date(0),t._useUTC?(n.setUTCFullYear(s[0],s[1],s[2]),n.setUTCHours(s[3],s[4],s[5],s[6])):(n.setFullYear(s[0],s[1],s[2]),n.setHours(s[3],s[4],s[5],s[6])),t._d=n}}function w(t){var e,n,s=t._f.match(E),i=t._i;for(t._a=[],e=0;eo&&(u=o,s=n);a(t,s)}function v(t){var e,n=t._i,s=K.exec(n);if(s){for(t._f="YYYY-MM-DD"+(s[2]||" "),e=0;4>e;e++)if(te[e][1].exec(n)){t._f+=te[e][0];break}j.exec(n)&&(t._f+=" Z"),w(t)}else t._d=new Date(n)}function T(e){var n=e._i,s=G.exec(n);n===t?e._d=new Date:s?e._d=new Date(+s[1]):"string"==typeof n?v(e):d(n)?(e._a=n.slice(0),Y(e)):e._d=n instanceof Date?new Date(+n):new Date(n)}function b(t,e,n,s,i){return i.relativeTime(e||1,!!n,t,s)}function S(t,e,n){var s=W(Math.abs(t)/1e3),i=W(s/60),r=W(i/60),a=W(r/24),o=W(a/365),u=45>s&&["s",s]||1===i&&["m"]||45>i&&["mm",i]||1===r&&["h"]||22>r&&["hh",r]||1===a&&["d"]||25>=a&&["dd",a]||45>=a&&["M"]||345>a&&["MM",W(a/30)]||1===o&&["y"]||["yy",o];return u[2]=e,u[3]=t>0,u[4]=n,b.apply({},u)}function F(t,e,n){var s,i=n-e,r=n-t.day();return r>i&&(r-=7),i-7>r&&(r+=7),s=H(t).add("d",r),{week:Math.ceil(s.dayOfYear()/7),year:s.year()}}function O(t){var e=t._i,n=t._f;return null===e||""===e?null:("string"==typeof e&&(t._i=e=_().preparse(e)),H.isMoment(e)?(t=a({},e),t._d=new Date(+e._d)):n?d(n)?k(t):w(t):T(t),new i(t))}function z(t,e){H.fn[t]=H.fn[t+"s"]=function(t){var n=this._isUTC?"UTC":"";return null!=t?(this._d["set"+n+e](t),H.updateOffset(this),this):this._d["get"+n+e]()}}function C(t){H.duration.fn[t]=function(){return this._data[t]}}function L(t,e){H.duration.fn["as"+t]=function(){return+this/e}}for(var H,P,U="2.1.0",W=Math.round,x={},A="undefined"!=typeof module&&module.exports,G=/^\/?Date\((\-?\d+)/i,Z=/(\-)?(\d*)?\.?(\d+)\:(\d+)\:(\d+)\.?(\d{3})?/,E=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,N=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,J=/\d\d?/,I=/\d{1,3}/,V=/\d{3}/,X=/\d{1,4}/,$=/[+\-]?\d{1,6}/,R=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,j=/Z|[\+\-]\d\d:?\d\d/i,q=/T/i,B=/[\+\-]?\d+(\.\d{1,3})?/,K=/^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,Q="YYYY-MM-DDTHH:mm:ssZ",te=[["HH:mm:ss.S",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ee=/([\+\-]|\d\d)/gi,ne="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),se={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},ie={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",w:"week",M:"month",y:"year"},re={},ae="DDD w W M D d".split(" "),oe="M D H h m s w W".split(" "),ue={M:function(){return this.month()+1},MMM:function(t){return this.lang().monthsShort(this,t)},MMMM:function(t){return this.lang().months(this,t)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(t){return this.lang().weekdaysMin(this,t)},ddd:function(t){return this.lang().weekdaysShort(this,t)},dddd:function(t){return this.lang().weekdays(this,t)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return u(this.year()%100,2)},YYYY:function(){return u(this.year(),4)},YYYYY:function(){return u(this.year(),5)},gg:function(){return u(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return u(this.weekYear(),5)},GG:function(){return u(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return u(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return~~(this.milliseconds()/100)},SS:function(){return u(~~(this.milliseconds()/10),2)},SSS:function(){return u(this.milliseconds(),3)},Z:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(t/60),2)+":"+u(~~t%60,2)},ZZ:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(10*t/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}};ae.length;)P=ae.pop(),ue[P+"o"]=n(ue[P],P);for(;oe.length;)P=oe.pop(),ue[P+P]=e(ue[P],2);for(ue.DDDD=e(ue.DDD,3),s.prototype={set:function(t){var e,n;for(n in t)e=t[n],"function"==typeof e?this[n]=e:this["_"+n]=e},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(t){return this._months[t.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(t){return this._monthsShort[t.month()]},monthsParse:function(t){var e,n,s;for(this._monthsParse||(this._monthsParse=[]),e=0;12>e;e++)if(this._monthsParse[e]||(n=H([2e3,e]),s="^"+this.months(n,"")+"|^"+this.monthsShort(n,""),this._monthsParse[e]=new RegExp(s.replace(".",""),"i")),this._monthsParse[e].test(t))return e},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(t){return this._weekdays[t.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(t){return this._weekdaysShort[t.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(t){return this._weekdaysMin[t.day()]},weekdaysParse:function(t){var e,n,s;for(this._weekdaysParse||(this._weekdaysParse=[]),e=0;7>e;e++)if(this._weekdaysParse[e]||(n=H([2e3,1]).day(e),s="^"+this.weekdays(n,"")+"|^"+this.weekdaysShort(n,"")+"|^"+this.weekdaysMin(n,""),this._weekdaysParse[e]=new RegExp(s.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(t){var e=this._longDateFormat[t];return!e&&this._longDateFormat[t.toUpperCase()]&&(e=this._longDateFormat[t.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t]=e),e},isPM:function(t){return"p"===(t+"").toLowerCase()[0]},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(t,e){var n=this._calendar[t];return"function"==typeof n?n.apply(e):n},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(t,e,n,s){var i=this._relativeTime[n];return"function"==typeof i?i(t,e,n,s):i.replace(/%d/i,t)},pastFuture:function(t,e){var n=this._relativeTime[t>0?"future":"past"];return"function"==typeof n?n(e):n.replace(/%s/i,e)},ordinal:function(t){return this._ordinal.replace("%d",t)},_ordinal:"%d",preparse:function(t){return t},postformat:function(t){return t},week:function(t){return F(t,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6}},H=function(t,e,n){return O({_i:t,_f:e,_l:n,_isUTC:!1})},H.utc=function(t,e,n){return O({_useUTC:!0,_isUTC:!0,_l:n,_i:t,_f:e})},H.unix=function(t){return H(1e3*t)},H.duration=function(t,e){var n,s,i=H.isDuration(t),a="number"==typeof t,o=i?t._input:a?{}:t,u=Z.exec(t);return a?e?o[e]=t:o.milliseconds=t:u&&(n="-"===u[1]?-1:1,o={y:0,d:~~u[2]*n,h:~~u[3]*n,m:~~u[4]*n,s:~~u[5]*n,ms:~~u[6]*n}),s=new r(o),i&&t.hasOwnProperty("_lang")&&(s._lang=t._lang),s},H.version=U,H.defaultFormat=Q,H.updateOffset=function(){},H.lang=function(t,e){return t?(e?l(t,e):x[t]||_(t),H.duration.fn._lang=H.fn._lang=_(t),void 0):H.fn._lang._abbr},H.langData=function(t){return t&&t._lang&&t._lang._abbr&&(t=t._lang._abbr),_(t)},H.isMoment=function(t){return t instanceof i},H.isDuration=function(t){return t instanceof r},H.fn=i.prototype={clone:function(){return H(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return M(H(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var t=this;return[t.year(),t.month(),t.date(),t.hours(),t.minutes(),t.seconds(),t.milliseconds()]},isValid:function(){return null==this._isValid&&(this._isValid=this._a?!c(this._a,(this._isUTC?H.utc(this._a):H(this._a)).toArray()):!isNaN(this._d.getTime())),!!this._isValid},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(t){var e=M(this,t||H.defaultFormat);return this.lang().postformat(e)},add:function(t,e){var n;return n="string"==typeof t?H.duration(+e,t):H.duration(t,e),h(this,n,1),this},subtract:function(t,e){var n;return n="string"==typeof t?H.duration(+e,t):H.duration(t,e),h(this,n,-1),this},diff:function(t,e,n){var s,i,r=this._isUTC?H(t).zone(this._offset||0):H(t).local(),a=6e4*(this.zone()-r.zone());return e=f(e),"year"===e||"month"===e?(s=432e5*(this.daysInMonth()+r.daysInMonth()),i=12*(this.year()-r.year())+(this.month()-r.month()),i+=(this-H(this).startOf("month")-(r-H(r).startOf("month")))/s,i-=6e4*(this.zone()-H(this).startOf("month").zone()-(r.zone()-H(r).startOf("month").zone()))/s,"year"===e&&(i/=12)):(s=this-r,i="second"===e?s/1e3:"minute"===e?s/6e4:"hour"===e?s/36e5:"day"===e?(s-a)/864e5:"week"===e?(s-a)/6048e5:s),n?i:o(i)},from:function(t,e){return H.duration(this.diff(t)).lang(this.lang()._abbr).humanize(!e)},fromNow:function(t){return this.from(H(),t)},calendar:function(){var t=this.diff(H().startOf("day"),"days",!0),e=-6>t?"sameElse":-1>t?"lastWeek":0>t?"lastDay":1>t?"sameDay":2>t?"nextDay":7>t?"nextWeek":"sameElse";return this.format(this.lang().calendar(e,this))},isLeapYear:function(){var t=this.year();return 0===t%4&&0!==t%100||0===t%400},isDST:function(){return this.zone()+H(t).startOf(e)},isBefore:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)<+H(t).startOf(e)},isSame:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)===+H(t).startOf(e)},min:function(t){return t=H.apply(null,arguments),this>t?this:t},max:function(t){return t=H.apply(null,arguments),t>this?this:t},zone:function(t){var e=this._offset||0;return null==t?this._isUTC?e:this._d.getTimezoneOffset():("string"==typeof t&&(t=p(t)),Math.abs(t)<16&&(t=60*t),this._offset=t,this._isUTC=!0,e!==t&&h(this,H.duration(e-t,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},daysInMonth:function(){return H.utc([this.year(),this.month()+1,0]).date()},dayOfYear:function(t){var e=W((H(this).startOf("day")-H(this).startOf("year"))/864e5)+1;return null==t?e:this.add("d",t-e)},weekYear:function(t){var e=F(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==t?e:this.add("y",t-e)},isoWeekYear:function(t){var e=F(this,1,4).year;return null==t?e:this.add("y",t-e)},week:function(t){var e=this.lang().week(this);return null==t?e:this.add("d",7*(t-e))},isoWeek:function(t){var e=F(this,1,4).week;return null==t?e:this.add("d",7*(t-e))},weekday:function(t){var e=(this._d.getDay()+7-this.lang()._week.dow)%7;return null==t?e:this.add("d",t-e)},isoWeekday:function(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)},lang:function(e){return e===t?this._lang:(this._lang=_(e),this)}},P=0;Pthis.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('').insertBefore(e(this)).on("click",r),s.toggleClass("open")),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:''}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
--------------------------------------------------------------------------------