├── .gitignore ├── public_html ├── img │ ├── homer.gif │ ├── blackorchid.png │ ├── grey_wash_wall.png │ ├── simple_dashed.png │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png ├── js │ ├── jquery.smooth-scroll.min.js │ ├── status.js │ ├── bootstrap.min.js │ └── sparkline.js ├── index.html └── css │ ├── bootstrap-responsive.min.css │ └── bootstrap-responsive.css ├── README.md ├── config.json ├── package.json ├── LICENSE ├── ingests.js ├── routes.js ├── twitchstatus.js ├── http.js ├── chat-servers.js ├── tmi-connection-handler.js └── chat.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /public_html/img/homer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/night/TwitchStatus/HEAD/public_html/img/homer.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TwitchStatus 2 | ============ 3 | 4 | Feel free to submit pull requests to add or improve features. -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "irc": { 3 | "username": "", 4 | "client_id": "", 5 | "access_token": "" 6 | } 7 | } -------------------------------------------------------------------------------- /public_html/img/blackorchid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/night/TwitchStatus/HEAD/public_html/img/blackorchid.png -------------------------------------------------------------------------------- /public_html/img/grey_wash_wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/night/TwitchStatus/HEAD/public_html/img/grey_wash_wall.png -------------------------------------------------------------------------------- /public_html/img/simple_dashed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/night/TwitchStatus/HEAD/public_html/img/simple_dashed.png -------------------------------------------------------------------------------- /public_html/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/night/TwitchStatus/HEAD/public_html/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public_html/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/night/TwitchStatus/HEAD/public_html/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitchstatus", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "Unofficial Twitch service status", 6 | "license": "MIT", 7 | "dependencies": { 8 | "async": "^2.0.0-rc.1", 9 | "express": "~4.13.4", 10 | "express-toobusy": "0.0.3", 11 | "irc-message": "^3.0.1", 12 | "node-media-server": "^2.1.9", 13 | "request": ">=2.69.0", 14 | "ws": "^7.2.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 NightDev, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ingests.js: -------------------------------------------------------------------------------- 1 | var request = require("request"); 2 | var config = require("./config.json"); 3 | 4 | module.exports = function (callback) { 5 | request( 6 | { 7 | url: "https://ingest.twitch.tv/ingests", 8 | headers: { 9 | "Client-ID": config.irc.client_id, 10 | }, 11 | json: true, 12 | timeout: 60000, 13 | }, 14 | function (error, response, data) { 15 | if (error || !data.ingests) { 16 | callback([]); 17 | return; 18 | } 19 | 20 | var servers = []; 21 | data.ingests.forEach(function (ingest) { 22 | var host = ingest.url_template.split("/")[2], 23 | port = 1935; 24 | 25 | var server = { 26 | name: host 27 | .replace(/^live/, "Live") 28 | .replace(".justin.", ".twitch.") 29 | .replace(".twitch.", ".Twitch.") 30 | .replace(".tv", ".TV"), 31 | type: "ingest", 32 | description: ingest.name 33 | .replace("Midwest", "Central") 34 | .replace("Asia", "AS") 35 | .replace("Australia", "AU"), 36 | host: host, 37 | port: port, 38 | }; 39 | 40 | servers.push(server); 41 | }); 42 | 43 | servers.sort(function (a, b) { 44 | return a.description.localeCompare(b.description); 45 | }); 46 | 47 | callback(servers); 48 | } 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | module.exports = function (main) { 2 | var app = main.app, 3 | servers = main.servers; 4 | 5 | var getAlerts = function (type) { 6 | var alerts = []; 7 | 8 | Object.keys(servers).forEach(function (name) { 9 | var server = servers[name]; 10 | 11 | if (server.type !== type) return; 12 | 13 | if (server.alerts.length > 0) { 14 | server.alerts.forEach(function (alert) { 15 | alerts.push({ 16 | server: name, 17 | type: alert.type, 18 | message: alert.message, 19 | }); 20 | }); 21 | } 22 | }); 23 | 24 | return alerts; 25 | }; 26 | 27 | var formatServers = function (type) { 28 | var formatted = []; 29 | 30 | Object.keys(servers).forEach(function (name) { 31 | var server = servers[name]; 32 | 33 | if (server.type !== type) return; 34 | 35 | formatted.push({ 36 | server: name, 37 | cluster: server.cluster, 38 | host: server.type !== "chat" ? server.host : undefined, 39 | ip: server.type === "chat" ? server.host : undefined, 40 | secure: server.secure, 41 | port: server.port, 42 | protocol: server.protocol, 43 | description: server.description, 44 | status: server.status, 45 | loadTime: server.type !== "chat" ? server.lag : undefined, 46 | errors: server.errors ? server.errors.total : undefined, 47 | lag: server.type === "chat" ? server.lag : undefined, 48 | pings: server.type === "chat" ? server.pings : undefined, 49 | }); 50 | }); 51 | 52 | return formatted; 53 | }; 54 | 55 | app.get("/api/status", function (req, res) { 56 | switch (req.query.type) { 57 | case "web": 58 | case "ingest": 59 | case "chat": 60 | res.jsonp(formatServers(req.query.type)); 61 | break; 62 | default: 63 | res.jsonp({ 64 | web: { 65 | alerts: [], 66 | servers: formatServers("web"), 67 | }, 68 | ingest: { 69 | alerts: [], 70 | servers: formatServers("ingest"), 71 | }, 72 | chat: { 73 | alerts: getAlerts("chat"), 74 | servers: formatServers("chat"), 75 | }, 76 | }); 77 | return; 78 | } 79 | }); 80 | 81 | // Catch-all not found 82 | app.get("*", function (req, res) { 83 | res.status(404).send("404 Not Found"); 84 | }); 85 | }; 86 | -------------------------------------------------------------------------------- /public_html/js/jquery.smooth-scroll.min.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Smooth Scroll - v1.4.10 - 2013-03-02 4 | * https://github.com/kswedberg/jquery-smooth-scroll 5 | * Copyright (c) 2013 Karl Swedberg 6 | * Licensed MIT (https://github.com/kswedberg/jquery-smooth-scroll/blob/master/LICENSE-MIT) 7 | */ 8 | (function(l){function t(l){return l.replace(/(:|\.)/g,"\\$1")}var e="1.4.10",o={exclude:[],excludeWithin:[],offset:0,direction:"top",scrollElement:null,scrollTarget:null,beforeScroll:function(){},afterScroll:function(){},easing:"swing",speed:400,autoCoefficent:2},r=function(t){var e=[],o=!1,r=t.dir&&"left"==t.dir?"scrollLeft":"scrollTop";return this.each(function(){if(this!=document&&this!=window){var t=l(this);t[r]()>0?e.push(this):(t[r](1),o=t[r]()>0,o&&e.push(this),t[r](0))}}),e.length||this.each(function(){"BODY"===this.nodeName&&(e=[this])}),"first"===t.el&&e.length>1&&(e=[e[0]]),e};l.fn.extend({scrollable:function(l){var t=r.call(this,{dir:l});return this.pushStack(t)},firstScrollable:function(l){var t=r.call(this,{el:"first",dir:l});return this.pushStack(t)},smoothScroll:function(e){e=e||{};var o=l.extend({},l.fn.smoothScroll.defaults,e),r=l.smoothScroll.filterPath(location.pathname);return this.unbind("click.smoothscroll").bind("click.smoothscroll",function(e){var n=this,s=l(this),c=o.exclude,i=o.excludeWithin,a=0,f=0,h=!0,u={},d=location.hostname===n.hostname||!n.hostname,m=o.scrollTarget||(l.smoothScroll.filterPath(n.pathname)||r)===r,p=t(n.hash);if(o.scrollTarget||d&&m&&p){for(;h&&c.length>a;)s.is(t(c[a++]))&&(h=!1);for(;h&&i.length>f;)s.closest(i[f++]).length&&(h=!1)}else h=!1;h&&(e.preventDefault(),l.extend(u,o,{scrollTarget:o.scrollTarget||p,link:n}),l.smoothScroll(u))}),this}}),l.smoothScroll=function(t,e){var o,r,n,s,c=0,i="offset",a="scrollTop",f={},h={};"number"==typeof t?(o=l.fn.smoothScroll.defaults,n=t):(o=l.extend({},l.fn.smoothScroll.defaults,t||{}),o.scrollElement&&(i="position","static"==o.scrollElement.css("position")&&o.scrollElement.css("position","relative"))),o=l.extend({link:null},o),a="left"==o.direction?"scrollLeft":a,o.scrollElement?(r=o.scrollElement,c=r[a]()):r=l("html, body").firstScrollable(),o.beforeScroll.call(r,o),n="number"==typeof t?t:e||l(o.scrollTarget)[i]()&&l(o.scrollTarget)[i]()[o.direction]||0,f[a]=n+c+o.offset,s=o.speed,"auto"===s&&(s=f[a]||r.scrollTop(),s/=o.autoCoefficent),h={duration:s,easing:o.easing,complete:function(){o.afterScroll.call(o.link,o)}},o.step&&(h.step=o.step),r.length?r.stop().animate(f,h):o.afterScroll.call(o.link,o)},l.smoothScroll.version=e,l.smoothScroll.filterPath=function(l){return l.replace(/^\//,"").replace(/(index|default).[a-zA-Z]{3,4}$/,"").replace(/\/$/,"")},l.fn.smoothScroll.defaults=o})(jQuery); -------------------------------------------------------------------------------- /twitchstatus.js: -------------------------------------------------------------------------------- 1 | var chat = require("./chat"), 2 | chatServers = require("./chat-servers"), 3 | config = require("./config.json"), 4 | express = require("express"), 5 | http = require("./http"), 6 | ingests = require("./ingests"); 7 | 8 | TwitchStatus = function () { 9 | this.app = express(); 10 | 11 | process.on("uncaughtException", function (err) { 12 | console.log("Caught exception: " + err); 13 | console.log(err.stack); 14 | }); 15 | 16 | this.app.listen(8080, "localhost"); 17 | this.app.disable("x-powered-by"); 18 | this.app.use(express.static(__dirname + "/public_html")); 19 | this.app.use("/", express.static(__dirname + "/public_html/index.html")); 20 | 21 | this._servers = [ 22 | { 23 | name: "Twitch.TV", 24 | type: "web", 25 | description: "Twitch's main website", 26 | host: "www.twitch.tv", 27 | path: "/", 28 | port: 443, 29 | }, 30 | { 31 | name: "API.Twitch.TV", 32 | type: "web", 33 | description: "Twitch's external endpoint for data retrieval", 34 | host: "api.twitch.tv", 35 | path: "/helix/streams", 36 | port: 443, 37 | }, 38 | ]; 39 | this.servers = {}; 40 | 41 | this.reports = []; 42 | this.lostMessages = []; 43 | 44 | this.chat = new chat(this); 45 | this.http = new http(this); 46 | 47 | var _self = this; 48 | ingests(function (servers) { 49 | _self._servers = _self._servers.concat(servers); 50 | 51 | chatServers(function (servers) { 52 | _self._servers = _self._servers.concat(servers); 53 | 54 | _self.setup(); 55 | }); 56 | }); 57 | 58 | setInterval(function () { 59 | _self.cleanup(); 60 | }, 30000); 61 | }; 62 | 63 | TwitchStatus.prototype.setup = function () { 64 | // Setup server monitoring 65 | for (var i = 0; i < this._servers.length; i++) { 66 | var server = this._servers[i]; 67 | 68 | this.servers[server.name] = { 69 | name: server.name, 70 | type: server.type, 71 | description: server.description, 72 | host: server.host.toLowerCase(), 73 | port: server.port, 74 | path: server.path, 75 | cluster: server.cluster || undefined, 76 | channel: server.channel || undefined, 77 | protocol: server.protocol || undefined, 78 | secure: server.secure || false, 79 | status: "unknown", 80 | lag: 999999, 81 | }; 82 | 83 | if (server.type === "chat") { 84 | this.chat.setup(server); 85 | } 86 | } 87 | 88 | this.http.checkStatus(); 89 | 90 | // Setup express routes 91 | require("./routes")(this); 92 | }; 93 | 94 | TwitchStatus.prototype.cleanup = function () { 95 | var current = Math.round(Date.now() / 1000), 96 | past5 = (current - 300) * 1000, 97 | limit = new Date(past5); 98 | 99 | for (var i = this.reports.length - 1; i >= 0; i--) { 100 | if (this.reports[i].logged < limit) { 101 | this.reports.splice(i, 1); 102 | } 103 | } 104 | 105 | for (var i = this.lostMessages.length - 1; i >= 0; i--) { 106 | if (this.lostMessages[i].sent < limit) { 107 | this.lostMessages.splice(i, 1); 108 | } 109 | } 110 | }; 111 | 112 | new TwitchStatus(); 113 | -------------------------------------------------------------------------------- /http.js: -------------------------------------------------------------------------------- 1 | var request = require("request"); 2 | var config = require("./config.json"); 3 | var NodeRtmpClient = require("node-media-server/node_rtmp_client"); 4 | 5 | var HTTP = function (main) { 6 | this.servers = main.servers; 7 | 8 | var _self = this; 9 | setInterval(function () { 10 | _self.checkStatus(); 11 | }, 30000); 12 | }; 13 | 14 | HTTP.prototype.checkStatus = function () { 15 | var servers = this.servers, 16 | t = 0; 17 | 18 | var _self = this; 19 | Object.keys(servers).forEach(function (name) { 20 | var server = servers[name]; 21 | if (server.type === "chat") return; 22 | 23 | if (server.type === "ingest") { 24 | setTimeout(function () { 25 | _self.checkIngestAddress.call( 26 | _self, 27 | server.name, 28 | server.host, 29 | server.port 30 | ); 31 | }, (t += 1000)); 32 | return; 33 | } 34 | 35 | setTimeout(function () { 36 | _self.checkWebAddress.call( 37 | _self, 38 | server.name, 39 | server.host, 40 | server.port, 41 | server.path 42 | ); 43 | }, (t += 1000)); 44 | }); 45 | }; 46 | 47 | HTTP.prototype.checkWebAddress = function (name, host, port, path) { 48 | var servers = this.servers, 49 | startTime = Date.now(), 50 | path = path || "/", 51 | url = "http" + (port === 443 ? "s" : "") + "://" + host + ":" + port + path, 52 | isAPI = host.toLowerCase() === "api.twitch.tv"; 53 | 54 | request.get( 55 | { 56 | url: url, 57 | qs: { 58 | kappa: Math.random(), 59 | }, 60 | headers: isAPI 61 | ? { 62 | "Client-ID": config.irc.client_id, 63 | Authorization: "Bearer " + config.irc.access_token, 64 | } 65 | : undefined, 66 | timeout: 30000, 67 | }, 68 | function (err, res, body) { 69 | if (!err && res.statusCode === 200) { 70 | servers[name].lag = Date.now() - startTime; 71 | servers[name].status = "online"; 72 | 73 | // I consider >= 3000 slow because of CDNs 74 | // 1935 is an ingest port, but some ingests are across the world 75 | if (servers[name].lag >= 3000 && port !== 1935) { 76 | servers[name].status = "slow"; 77 | } 78 | } else { 79 | servers[name].lag = 999999; 80 | servers[name].status = "offline"; 81 | } 82 | } 83 | ); 84 | }; 85 | 86 | HTTP.prototype.checkIngestAddress = function (name, host, port) { 87 | var servers = this.servers; 88 | var startTime = Date.now(); 89 | var client = new NodeRtmpClient( 90 | "rtmp://" + host + ":" + port + "/app/fakestreamkey" 91 | ); 92 | 93 | // we destroy the connection before attempting to read from a stream key 94 | client.rtmpSendCreateStream = function () {}; 95 | 96 | client.on("status", function (info) { 97 | switch (info.code) { 98 | case "NetConnection.Connect.Success": 99 | servers[name].lag = Date.now() - startTime; 100 | servers[name].status = "online"; 101 | client.socket.destroy(); 102 | break; 103 | default: 104 | console.log("unhandled ingest info", info); 105 | break; 106 | } 107 | }); 108 | 109 | client.startPull(); 110 | client.socket.on("error", () => { 111 | servers[name].lag = 999999; 112 | servers[name].status = "offline"; 113 | }); 114 | }; 115 | 116 | module.exports = HTTP; 117 | -------------------------------------------------------------------------------- /chat-servers.js: -------------------------------------------------------------------------------- 1 | var config = require("./config.json"), 2 | request = require("request"), 3 | dns = require("dns"), 4 | async = require("async"); 5 | 6 | String.prototype.capitalize = function () { 7 | return this.charAt(0).toUpperCase() + this.slice(1); 8 | }; 9 | 10 | var parseServers = function (res, callback) { 11 | var uniqueHosts = []; 12 | var servers = []; 13 | 14 | async.each( 15 | ["servers", "websockets_servers"], 16 | function (type, callback) { 17 | if (!res[type]) return callback(); 18 | 19 | async.each( 20 | res[type], 21 | function (server, callback) { 22 | var subServers = []; 23 | 24 | async.waterfall( 25 | [ 26 | function (callback) { 27 | var host = server.split(":")[0]; 28 | var port = parseInt(server.split(":")[1]); 29 | 30 | if (/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(host)) { 31 | subServers.push({ 32 | host: host, 33 | port: port, 34 | }); 35 | return callback(); 36 | } 37 | 38 | dns.lookup( 39 | host, 40 | { 41 | all: true, 42 | }, 43 | function (err, hosts) { 44 | if (err) return callback(); 45 | 46 | subServers = subServers.concat( 47 | hosts.map(function (h) { 48 | return { 49 | host: h.address, 50 | port: port, 51 | }; 52 | }) 53 | ); 54 | 55 | callback(); 56 | } 57 | ); 58 | }, 59 | function (callback) { 60 | subServers.forEach(function (server) { 61 | if (server.port === 6667) { 62 | subServers.push({ 63 | host: server.host, 64 | port: 6697, 65 | }); 66 | } else if (server.port === 80) { 67 | subServers.push({ 68 | host: server.host, 69 | port: 443, 70 | }); 71 | } 72 | }); 73 | subServers.forEach(function (server) { 74 | var hostPort = server.host + ":" + server.port; 75 | 76 | if (uniqueHosts.indexOf(hostPort) > -1) return; 77 | uniqueHosts.push(hostPort); 78 | 79 | servers.push({ 80 | name: hostPort, 81 | type: "chat", 82 | cluster: "aws", 83 | protocol: type === "websockets_servers" ? "ws_irc" : "irc", 84 | description: "Chat Server", 85 | host: server.host, 86 | port: server.port, 87 | secure: [443, 6697].indexOf(server.port) > -1, 88 | }); 89 | }); 90 | 91 | callback(); 92 | }, 93 | ], 94 | callback 95 | ); 96 | }, 97 | callback 98 | ); 99 | }, 100 | function () { 101 | callback(servers); 102 | } 103 | ); 104 | }; 105 | 106 | var chatServers = function (callback) { 107 | request( 108 | { 109 | url: "https://tmi.twitch.tv/servers", 110 | qs: { 111 | channel: config.irc.username, 112 | kappa: Math.random(), 113 | }, 114 | json: true, 115 | timeout: 60000, 116 | }, 117 | function (error, response, data) { 118 | if (error || response.statusCode !== 200) { 119 | callback([]); 120 | return; 121 | } 122 | 123 | parseServers(data, function (newServers) { 124 | callback(newServers); 125 | }); 126 | } 127 | ); 128 | }; 129 | 130 | module.exports = function (callback) { 131 | chatServers(function (servers) { 132 | servers.sort(function (a, b) { 133 | return a.port - b.port || a.host.localeCompare(b.host); 134 | }); 135 | 136 | callback(servers); 137 | }); 138 | }; 139 | -------------------------------------------------------------------------------- /tmi-connection-handler.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require("events").EventEmitter; 2 | var net = require("net"); 3 | var tls = require("tls"); 4 | var parse = require("irc-message").parse; 5 | var util = require("util"); 6 | var ws = require("ws"); 7 | 8 | var IRC = function (options) { 9 | EventEmitter.call(this); 10 | 11 | this.options = options; 12 | this._socket = null; 13 | this._connected = false; 14 | this._buffer = null; 15 | 16 | if (!this.options.host) { 17 | throw new Error("No host configured"); 18 | return; 19 | } 20 | 21 | if (!this.options.port) { 22 | throw new Error("No port configured"); 23 | return; 24 | } 25 | 26 | if (!this.options.nick) { 27 | throw new Error("No nick configured"); 28 | return; 29 | } 30 | 31 | if (!this.options.protocol) { 32 | throw new Error("No protocol configured"); 33 | return; 34 | } 35 | }; 36 | 37 | util.inherits(IRC, EventEmitter); 38 | 39 | // IRC Connections 40 | 41 | IRC.prototype._wsConnect = function () { 42 | var _self = this; 43 | var protocol = this.options.secure ? "wss:" : "ws:"; 44 | var socket = new ws( 45 | protocol + "//" + this.options.host + ":" + this.options.port, 46 | "irc", 47 | { 48 | localAddress: this.options.localAddress, 49 | rejectUnauthorized: false, 50 | } 51 | ); 52 | 53 | socket.on("open", function () { 54 | _self._onOpen(); 55 | }); 56 | socket.on("message", function (data) { 57 | _self._onData(data); 58 | }); 59 | socket.on("close", function () { 60 | _self._onClose(); 61 | }); 62 | socket.on("error", function () { 63 | _self._onClose(); 64 | }); 65 | 66 | this._socket = socket; 67 | }; 68 | 69 | IRC.prototype._ircConnect = function () { 70 | var _self = this; 71 | var protocol = this.options.secure ? tls : net; 72 | var socket = protocol.connect( 73 | { 74 | host: this.options.host, 75 | port: this.options.port, 76 | localAddress: this.options.localAddress, 77 | rejectUnauthorized: false, 78 | }, 79 | function () { 80 | _self._onOpen(); 81 | } 82 | ); 83 | 84 | socket.on("data", function (data) { 85 | _self._onData(data); 86 | }); 87 | socket.on("end", function () { 88 | _self._onClose(); 89 | }); 90 | socket.on("error", function () { 91 | _self._onClose(); 92 | }); 93 | socket.on("timeout", function () { 94 | _self._onTimeout(); 95 | }); 96 | 97 | this._socket = socket; 98 | }; 99 | 100 | IRC.prototype.connect = function () { 101 | if (this._socket) return this.reconnect(); 102 | return this.options.protocol === "irc" 103 | ? this._ircConnect() 104 | : this._wsConnect(); 105 | }; 106 | 107 | IRC.prototype.disconnect = function () { 108 | if (!this._connected) return; 109 | 110 | this._connected = false; 111 | this._closeSocket(); 112 | }; 113 | 114 | IRC.prototype.reconnect = function () { 115 | this._closeSocket(); 116 | this.connect(); 117 | }; 118 | 119 | IRC.prototype._closeSocket = function () { 120 | try { 121 | if (this.options.protocol === "irc") { 122 | this._socket.destroy(); 123 | } else { 124 | this._socket.close(); 125 | } 126 | } catch (e) {} 127 | 128 | delete this._socket; 129 | }; 130 | 131 | // IRC Parser 132 | 133 | IRC.prototype._onOpen = function () { 134 | this._connected = true; 135 | if (this.options.pass) this.raw("PASS", this.options.pass); 136 | this.raw("NICK", this.options.nick); 137 | this.raw("CAP", "REQ", ":twitch.tv/commands twitch.tv/tags"); 138 | this.emit("connected"); 139 | }; 140 | 141 | IRC.prototype._onData = function (data) { 142 | var lines = data.toString().split("\r\n"); 143 | 144 | if (this._buffer) { 145 | lines[0] = this._buffer + lines[0]; 146 | this._buffer = null; 147 | } 148 | 149 | if (lines[lines.length - 1] !== "") { 150 | this._buffer = lines.pop(); 151 | } 152 | 153 | for (var i = 0; i < lines.length; i++) { 154 | this._parse(lines[i]); 155 | } 156 | }; 157 | 158 | IRC.prototype._parse = function (message) { 159 | message = parse(message); 160 | 161 | if (!message) return; 162 | 163 | if (message.command === "PING") { 164 | this.raw("PONG", message.params.join(" ")); 165 | return; 166 | } 167 | 168 | var data = { 169 | target: message.params.shift(), 170 | nick: message.prefix 171 | ? message.prefix.split("@")[0].split("!")[0] 172 | : undefined, 173 | tags: message.tags, 174 | message: message.params.shift(), 175 | raw: message.raw, 176 | }; 177 | 178 | this.emit(message.command.toLowerCase(), data); 179 | }; 180 | 181 | IRC.prototype._onClose = function () { 182 | if (!this._connected) return; 183 | this._connected = false; 184 | this.emit("disconnected"); 185 | }; 186 | 187 | IRC.prototype._onTimeout = function () { 188 | this._connected = false; 189 | this.emit("disconnected"); 190 | }; 191 | 192 | // IRC Commands 193 | 194 | IRC.prototype._send = function () { 195 | if (!this._connected || !this._socket) return; 196 | 197 | if (this.options.protocol === "irc") { 198 | this._socket.write(Array.prototype.join.call(arguments, " ") + "\r\n"); 199 | } else { 200 | this._socket.send(Array.prototype.join.call(arguments, " ") + "\r\n"); 201 | } 202 | }; 203 | 204 | IRC.prototype.raw = IRC.prototype._send; 205 | 206 | IRC.prototype.join = function (channel) { 207 | this._send("JOIN", channel); 208 | }; 209 | 210 | IRC.prototype.part = function (channel) { 211 | this._send("PART", channel); 212 | }; 213 | 214 | IRC.prototype.privmsg = function (channel, message) { 215 | this._send("PRIVMSG", channel, ":" + message); 216 | }; 217 | 218 | module.exports = IRC; 219 | -------------------------------------------------------------------------------- /public_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Twitch Status 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 29 | 30 | 31 | 32 | 33 | 35 | 61 |
62 | 63 | 65 |
66 |
67 | 68 |
69 |
70 |

About

71 |

Twitch sometimes has "twitches" in its infrastructure, and their response time to outages can be lacking. This page serves as a tool to unofficially check on service problems at Twitch.

72 |

73 |
74 |
75 | 76 |
77 |
78 |

Status Tracking

79 |

This site monitors the ingest servers, the chat servers, and the web services over at Twitch remotely. The statistics generated on this site will be gathered by a single server. We will attempt to provide accurate data measurements, but they could be inaccurate. Service statistics are updated every 30 seconds.

80 |
81 |
82 | 83 |
84 |
85 | 86 | 88 |
89 | 92 | 93 |
94 | Web services are the front-end of the operation. The main website needs to be online to serve content to end users, and the API must be online for on-site features, third-party tools, and services to function properly. 95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
ServiceDescriptionStatusLoad Time
109 |
110 | 111 | 112 | 114 |
115 | 118 | 119 |
120 | The ingest servers are relatively stable creatures. There may be hiccups within the datacenters at which these are hosted that are undetectable by this website, and it's for this reason that Twitch Status only checks if they are reachable. 121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |
ServerLocationStatus
134 |
135 | 136 | 138 |
139 | 142 | 143 |
144 | This section of the site pertains to Twitch's chat infrastructure. A graph directly below tracks the number of chat lines being sent every second in all channels on Twitch. A table below also tracks chat status based on server IP and port. When Twitch is having issues, you'll see how badly the issue extends on the realtime graph. Note that while a chat server may state it's online during an interruption of service, it may not work for you. There are multiple factors that cause chat interruptions for Twitch, and the extent of disruptions vary case by case. 145 |
146 | 147 |
148 | 149 |
150 | 151 |
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |
ServerProtocolStatusErrors (5 min)Lag (Avg)Lag Graph
166 |
167 | 168 |
169 | 170 |



171 | 172 | 174 |
175 | 176 | 182 | 183 |
184 | 185 | 186 | 187 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /public_html/js/status.js: -------------------------------------------------------------------------------- 1 | $('.navbar').scrollspy(); 2 | $('.navbar a').smoothScroll(); 3 | 4 | function generateGraphs() { 5 | $('.graph span').each(function() { 6 | var pings = $(this).data('pings').split(','); 7 | for(var i=0; i'+d.server+''+d.description+'Loading..Loading..'); 33 | }); 34 | data.ingest.servers.forEach(function(d){ 35 | var serverName = d.server.replace(/(\.|\(|\)|:)/g,"_"); 36 | $("#ingestTable").append(''+d.server+''+d.description+'Loading..'); 37 | }); 38 | data.chat.servers.forEach(function(d){ 39 | var serverName = d.server.replace(/(\.|\(|\)|:)/g,"_"); 40 | var chatType = d.cluster; 41 | $("#chat_"+chatType+" tbody").append(''+d.server.replace(/:([0-9]+)/g," (Port $1") + (d.secure ? ', Secure)' : ')') +(d.description!=='Chat Server'?' — '+d.description:'')+' Loading..Loading..Loading..Loading..Loading..'); 42 | }); 43 | } 44 | data.web.servers.forEach(function(d){ 45 | if(d.status === "online") { 46 | d.status = 'Online'; 47 | } else if(d.status === "offline") { 48 | disruption++; 49 | d.status = 'Offline'; 50 | } else if(d.status === "slow") { 51 | d.status = 'Slow'; 52 | } else if(d.status === "unknown") { 53 | d.status = 'Unknown'; 54 | } 55 | if(d.loadTime >= 1000) { 56 | d.loadTime = parseFloat(d.loadTime/1000).toFixed(2) + " s"; 57 | } else { 58 | d.loadTime = d.loadTime + " ms"; 59 | } 60 | var serverName = d.server.replace(/(\.|\(|\)|:)/g,"_"), 61 | $server = $("#webServicesTable").children("tr[data-server='"+serverName+"']"); 62 | $server.children("td.status").html(d.status); 63 | $server.children("td.loadTime").html(d.loadTime); 64 | }); 65 | data.ingest.servers.forEach(function(d){ 66 | if(d.status === "online") { 67 | d.status = 'Online'; 68 | } else if(d.status === "offline") { 69 | disruption++; 70 | d.status = 'Offline'; 71 | } else if(d.status === "slow") { 72 | disruption++; 73 | d.status = 'Slow'; 74 | } else if(d.status === "unknown") { 75 | d.status = 'Unknown'; 76 | } 77 | var serverName = d.server.replace(/(\.|\(|\)|:)/g,"_"), 78 | $server = $("#ingestTable").children("tr[data-server='"+serverName+"']"); 79 | $server.children("td.status").html(d.status); 80 | }); 81 | data.chat.servers.forEach(function(d){ 82 | var offline = false; 83 | if(d.status === "online") { 84 | d.status = 'Online'; 85 | } else if(d.status === "offline") { 86 | offline = true; 87 | d.status = 'Offline'; 88 | } else if(d.status === "slow") { 89 | disruption++; 90 | d.status = 'Slow'; 91 | } else if(d.status === "unknown") { 92 | d.status = 'Unknown'; 93 | } 94 | if(offline === true) { 95 | d.lag = 'N/A'; 96 | d.errors = 'N/A'; 97 | d.pings = [0,0]; 98 | var lagColor = "#FF0000"; 99 | } else { 100 | if(d.errors <= 5) { 101 | d.errors = ''+d.errors+' errors'; 102 | } else if(d.errors <= 10) { 103 | d.errors = ''+d.errors+' errors'; 104 | } else if(d.errors > 10) { 105 | d.errors = ''+d.errors+' errors'; 106 | } 107 | if(d.lag >= 1000) { 108 | d.lagFormatted = parseFloat(d.lag/1000).toFixed(2) + " s"; 109 | } else { 110 | d.lagFormatted = d.lag + " ms"; 111 | } 112 | if(d.lag < 300) { 113 | d.lag = ''+d.lagFormatted+''; 114 | if(d.lag === 0) { 115 | var lagColor = "#FF0000"; 116 | } else { 117 | var lagColor = "#008800"; 118 | } 119 | } else if(d.lag <= 400) { 120 | d.lag = ''+d.lagFormatted+''; 121 | var lagColor = "#FFEF00"; 122 | } else if(d.lag > 400) { 123 | d.lag = ''+d.lagFormatted+''; 124 | var lagColor = "#FF0000"; 125 | } 126 | } 127 | if(d.pings.length < 2) { 128 | d.pings = [0,0]; 129 | } 130 | var serverName = d.server.replace(/(\.|\(|\)|:)/g,"_"), 131 | chatType = d.cluster; 132 | $server = $("#chat_"+chatType+" tbody").children("tr[data-server='"+serverName+"']"); 133 | $server.children("td").children(".alerts").html(''); 134 | $server.children("td.status").html(d.status); 135 | $server.children("td.errors").html(d.errors); 136 | $server.children("td.protocol").html(d.protocol); 137 | $server.children("td.lag").html(d.lag); 138 | var $graph = $(''); 139 | $graph.data('color', lagColor); 140 | $graph.data('pings', d.pings.toString()); 141 | $server.children("td.graph").html($graph); 142 | }); 143 | generateGraphs(); 144 | data.chat.alerts.forEach(function(alert) { 145 | var serverName = alert.server.replace(/(\.|\(|\)|:)/g,"_"), 146 | $server = $("tr[data-server='"+serverName+"']"); 147 | 148 | if($server.children("td.status").children("span").text() === "Offline") return; 149 | 150 | if(alert.message === 'Messages Lost') { 151 | if(alert.type === 'info') { 152 | messagesLost += 2; 153 | } else if(alert.type === 'warning') { 154 | messagesLost += 5; 155 | } else { 156 | messagesLost += 10; 157 | } 158 | } 159 | 160 | $server.children("td").children(".alerts").prepend(''+alert.message+'  '); 161 | }); 162 | if(data.chat.banned) { 163 | $('#banned-alert').fadeIn(); 164 | } else { 165 | $('#banned-alert').fadeOut(); 166 | } 167 | if(messagesLost >= 20) { 168 | $('#nomNomNom').fadeIn(); 169 | } else { 170 | $('#nomNomNom').fadeOut(); 171 | } 172 | if(disruption === 0) { 173 | $("#entireStatus").html('Online'); 174 | } else { 175 | $("#entireStatus").html('Disrupted'); 176 | } 177 | }); 178 | setTimeout(updateStatus, 60000); 179 | } 180 | 181 | $(document).ready(function () { 182 | updateStatus(); 183 | 184 | $('.nav-tabs li a').click(function() { 185 | setTimeout(generateGraphs, 500); 186 | }); 187 | }); 188 | -------------------------------------------------------------------------------- /chat.js: -------------------------------------------------------------------------------- 1 | var config = require("./config.json"), 2 | tmi = require("./tmi-connection-handler"); 3 | 4 | var Chat = function (main) { 5 | this.db = main.db; 6 | this.storedReports = main.reports; 7 | this.storedLostMessages = main.lostMessages; 8 | this.servers = main.servers; 9 | 10 | this.connectionQueue = []; 11 | 12 | this.lostMessages = {}; 13 | 14 | var _self = this; 15 | /*setInterval(function() { 16 | _self.updateStats(); 17 | }, 30000);*/ 18 | setInterval(function () { 19 | _self.pingChat(); 20 | }, 10000); 21 | setInterval(function () { 22 | if (!_self.connectionQueue.length) return; 23 | var tmi = _self.connectionQueue.shift(); 24 | tmi.connect(); 25 | }, 500); 26 | }; 27 | 28 | Chat.prototype.checkLostMessages = function (id) { 29 | var db = this.db, 30 | data = this.lostMessages[id]; 31 | 32 | var receivers = Object.keys(data.receivers), 33 | receiversArray = [], 34 | received = 0, 35 | receivedFromSelf = 0; 36 | 37 | // Check if the message was seen by all the other chat servers 38 | receivers.forEach(function (name) { 39 | var receiver = data.receivers[name]; 40 | 41 | if (receiver.received) { 42 | received++; 43 | if (name.split(":")[0] === data.origin.split(":")[0]) { 44 | // If same IP 45 | receivedFromSelf++; 46 | } 47 | } 48 | 49 | receiversArray.push({ 50 | server: name, 51 | ip: name.split(":")[0], 52 | port: parseInt(name.split(":")[1]), 53 | received: receiver.received, 54 | time: new Date(receiver.time), 55 | }); 56 | }); 57 | 58 | var percentReceived = parseFloat( 59 | ((received / receivers.length) * 100).toFixed(2) 60 | ), 61 | percentLost = parseFloat((100 - percentReceived).toFixed(2)); 62 | 63 | // If the message was completely lost or if the receiver only received its own messages, then the server itself is having issues. 64 | if ( 65 | percentLost === 100 || 66 | (receivers.length > 3 && received === receivedFromSelf) 67 | ) { 68 | this.storedReports.push({ 69 | type: "chat", 70 | kind: "lines", 71 | server: data.origin, 72 | logged: new Date(), 73 | }); 74 | receiversArray.forEach( 75 | function (receiver) { 76 | if (receiver.received) { 77 | this.storedReports.push({ 78 | type: "chat", 79 | kind: "lines", 80 | server: receiver.server, 81 | logged: new Date(), 82 | }); 83 | } 84 | }.bind(this) 85 | ); 86 | } else { 87 | receiversArray.forEach( 88 | function (receiver) { 89 | if (!receiver.received) { 90 | this.storedReports.push({ 91 | type: "chat", 92 | kind: "lines", 93 | server: receiver.server, 94 | logged: new Date(), 95 | }); 96 | } 97 | }.bind(this) 98 | ); 99 | } 100 | 101 | if (percentLost > 0) { 102 | this.storedLostMessages.push({ 103 | origin: { 104 | server: data.origin, 105 | ip: data.origin.split(":")[0], 106 | port: parseInt(data.origin.split(":")[1]), 107 | }, 108 | sent: new Date(data.sent), 109 | percentLost: percentLost, 110 | percentReceived: percentReceived, 111 | receivers: receiversArray, 112 | }); 113 | } 114 | 115 | // Remove message id 116 | delete this.lostMessages[id]; 117 | }; 118 | 119 | Chat.prototype.generateMessageID = function () { 120 | var text = "", 121 | possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 122 | 123 | for (var i = 0; i < 25; i++) { 124 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 125 | } 126 | 127 | return text; 128 | }; 129 | 130 | Chat.prototype.generateMessageLostObject = function (name) { 131 | var id = this.generateMessageID(), 132 | servers = this.servers, 133 | serverList = {}, 134 | time = Date.now(); 135 | 136 | // Generate a list of the other servers 137 | Object.keys(servers).forEach(function (server) { 138 | if (servers[server].type !== "chat") return; 139 | if (servers[server].description !== servers[name].description) return; 140 | 141 | serverList[server] = { 142 | received: false, 143 | time: null, 144 | }; 145 | }); 146 | 147 | var _self = this; 148 | setTimeout(function () { 149 | _self.checkLostMessages.call(_self, id); 150 | }, 60000); 151 | 152 | this.lostMessages[id] = { 153 | origin: name, 154 | sent: time, 155 | receivers: serverList, 156 | }; 157 | 158 | return { id: id, time: time }; 159 | }; 160 | 161 | Chat.prototype.pingChat = function () { 162 | var _self = this, 163 | servers = this.servers; 164 | 165 | Object.keys(servers).forEach(function (name) { 166 | var server = servers[name]; 167 | 168 | var channel = server.channel ? server.channel : "#" + config.irc.username; 169 | 170 | if (server.type === "chat") { 171 | if (server.pings.length > 50) { 172 | for (var i = 0; i < server.pings.length - 50; i++) { 173 | server.pings.shift(); 174 | } 175 | } 176 | if (server.firstMessage === true) { 177 | server.client.privmsg(channel, "firstmessage " + name + " 0 0 v3"); 178 | } else { 179 | var data = _self.generateMessageLostObject(name); 180 | server.client.privmsg( 181 | channel, 182 | name + " " + data.time + " " + data.id + " v3" 183 | ); 184 | } 185 | } 186 | }); 187 | }; 188 | 189 | Chat.prototype.setup = function (server) { 190 | var _self = this, 191 | server = this.servers[server.name]; 192 | 193 | server.joined = false; 194 | server.monitorjoined = false; 195 | server.pings = []; 196 | server.alerts = []; 197 | server.errors = { 198 | connection: 0, 199 | join: 0, 200 | lines: 0, 201 | total: 0, 202 | }; 203 | 204 | server.lastMessage = Date.now(); 205 | 206 | var channel = server.channel ? server.channel : "#" + config.irc.username; 207 | 208 | // We run two connections per server. One sender, One monitor 209 | server.client = new tmi({ 210 | host: server.host, 211 | port: server.port, 212 | nick: config.irc.username, 213 | pass: "oauth:" + config.irc.access_token, 214 | protocol: server.protocol, 215 | secure: server.secure, 216 | }); 217 | server.clientMonitor = new tmi({ 218 | host: server.host, 219 | port: server.port, 220 | nick: config.irc.username, 221 | pass: "oauth:" + config.irc.access_token, 222 | protocol: server.protocol, 223 | secure: server.secure, 224 | }); 225 | 226 | this.connectionQueue.push(server.client); 227 | this.connectionQueue.push(server.clientMonitor); 228 | 229 | // If there's a connection error, mark the server offline 230 | server.client.on("disconnected", function () { 231 | console.log( 232 | "%s disconnected on port %d over %s", 233 | server.host, 234 | server.port, 235 | server.protocol 236 | ); 237 | server.status = "offline"; 238 | _self.connectionQueue.push(server.client); 239 | }); 240 | server.clientMonitor.on("disconnected", function () { 241 | console.log( 242 | "%s disconnected on port %d over %s", 243 | server.host, 244 | server.port, 245 | server.protocol 246 | ); 247 | server.status = "offline"; 248 | _self.connectionQueue.push(server.clientMonitor); 249 | }); 250 | 251 | // When we get the MOTD (if we're disconnected), make sure we mark that we haven't joined yet. 252 | server.client.on("connected", function () { 253 | console.log( 254 | "%s connected on port %d over %s", 255 | server.host, 256 | server.port, 257 | server.protocol 258 | ); 259 | server.joined = false; 260 | server.firstMessage = true; 261 | server.client.join(channel); 262 | }); 263 | server.clientMonitor.on("connected", function () { 264 | console.log( 265 | "%s (monitor) connected on port %d over %s", 266 | server.host, 267 | server.port, 268 | server.protocol 269 | ); 270 | server.joined = false; 271 | server.clientMonitor.join(channel); 272 | }); 273 | 274 | // When we get NAMES, mark the channel as joined 275 | server.client.on("join", function () { 276 | server.joined = true; 277 | }); 278 | server.clientMonitor.on("join", function () { 279 | server.monitorJoined = true; 280 | }); 281 | 282 | // Parse incoming messages to test for "ping" 283 | server.clientMonitor.on("privmsg", function (data) { 284 | var from = data.nick; 285 | var message = data.message; 286 | var target = data.target; 287 | 288 | if (channel !== target) return; 289 | if (from !== config.irc.username) return; 290 | 291 | var timeNow = Date.now(); 292 | 293 | // For some reason, your first message always come through more quickly/reliably than the rest. 294 | // Lets just discard it. 295 | if (server.firstMessage) { 296 | server.firstMessage = false; 297 | return; 298 | } 299 | 300 | var params = message.split(" "); 301 | 302 | // Check if we have a valid message ID 303 | var lostMessageID = params[2]; 304 | if (!_self.lostMessages[lostMessageID]) return; 305 | 306 | // If we received a message, make sure we mark this server as working "ok" 307 | // by marking it as having received the message 308 | var receiver = _self.lostMessages[lostMessageID].receivers[server.name]; 309 | receiver.received = true; 310 | receiver.time = Date.now(); 311 | 312 | // If the server we got the message from is itself (we run 2 connections per server), 313 | // measure the "ping" 314 | if (server.name === params[0]) { 315 | // timeThen is the time the message was sent 316 | var timeThen = parseInt(message.split(" ")[1]); 317 | 318 | server.pings.push(timeNow - timeThen - 75); 319 | 320 | // Calculate average ping over past minute 321 | var avgPing = 0; 322 | if (server.pings.length <= 6) { 323 | server.pings.forEach(function (lag) { 324 | avgPing += lag; 325 | }); 326 | server.lag = Math.round(avgPing / server.pings.length); 327 | } else { 328 | for ( 329 | var i = server.pings.length - 1; 330 | i >= server.pings.length - 7; 331 | i-- 332 | ) { 333 | avgPing += server.pings[i]; 334 | } 335 | server.lag = Math.round(avgPing / 6); 336 | } 337 | 338 | // If messages take longer than 3 seconds to go through, the server is slow. 339 | // If the server has more than 20 errors, the server is slow. 340 | if (server.lag > 3000) { 341 | server.status = "slow"; 342 | } else if (server.errors.total >= 20) { 343 | server.status = "slow"; 344 | } else { 345 | server.status = "online"; 346 | } 347 | server.lastMessage = Date.now(); 348 | } 349 | }); 350 | 351 | setInterval(function () { 352 | // When joins fail, we need to try to join the channel again. 353 | if (server.monitorJoined === false) { 354 | server.clientMonitor.join(channel); 355 | } 356 | if (server.joined === false) { 357 | server.client.join(channel); 358 | } 359 | 360 | // If the connection seems inactive, mark the server as offline. 361 | var timeNow = Date.now(), 362 | timeThen = server.lastMessage; 363 | if (timeNow - timeThen > 120000) { 364 | server.status = "offline"; 365 | } 366 | }, 5000); 367 | 368 | setInterval(function () { 369 | // If the connection seems or is offline, we reconnect periodically. 370 | if (server.status === "offline") { 371 | server.client.disconnect(); 372 | server.clientMonitor.disconnect(); 373 | 374 | // Wait a short time before reconnecting. 375 | setTimeout(function () { 376 | _self.connectionQueue.push(server.client); 377 | _self.connectionQueue.push(server.clientMonitor); 378 | }, 5000); 379 | } 380 | }, 300000); 381 | }; 382 | 383 | Chat.prototype.updateStats = function () { 384 | var db = this.db, 385 | servers = this.servers; 386 | 387 | // Reset stats 388 | Object.keys(servers).forEach(function (name) { 389 | var server = servers[name]; 390 | if (server.type !== "chat") return; 391 | server.errors.connection = 0; 392 | server.errors.join = 0; 393 | server.errors.lines = 0; 394 | server.errors.total = 0; 395 | server.alerts = []; 396 | }); 397 | 398 | // Increment error counts 399 | this.storedReports.forEach(function (report) { 400 | if (!servers[report.server]) return; 401 | 402 | if (new Date(report.logged).getTime() > past60Time) { 403 | if (report.type === "chat" && report.kind === "join") { 404 | servers[report.server].errors.join++; 405 | } else if (report.type === "chat" && report.kind === "lines") { 406 | servers[report.server].errors.lines++; 407 | } 408 | } 409 | servers[report.server].errors.total++; 410 | }); 411 | 412 | // Generate alerts and set server statuses 413 | Object.keys(servers).forEach(function (name) { 414 | var server = servers[name]; 415 | if (server.type !== "chat") return; 416 | 417 | var type; 418 | if (server.errors.join > 0) { 419 | if (server.errors.join > 50) { 420 | type = "important"; 421 | } else if (server.errors.join > 25) { 422 | type = "warning"; 423 | } else { 424 | type = "info"; 425 | } 426 | server.alerts.push({ type: type, message: "Joins Failing" }); 427 | } 428 | if (server.errors.lines > 0) { 429 | if (server.errors.lines > 12) { 430 | type = "important"; 431 | } else if (server.errors.lines > 6) { 432 | type = "warning"; 433 | } else { 434 | type = "info"; 435 | } 436 | server.alerts.push({ type: type, message: "Messages Lost" }); 437 | } 438 | 439 | // If messages take longer than 3 seconds to go through, the server is slow. 440 | // If the server has more than 20 errors, the server is slow. 441 | if (server.status !== "offline") { 442 | if (server.errors.total <= 20 && server.lag <= 3000) { 443 | server.status = "online"; 444 | } else if (server.errors.total >= 20) { 445 | server.status = "slow"; 446 | } 447 | } 448 | }); 449 | }; 450 | 451 | module.exports = Chat; 452 | -------------------------------------------------------------------------------- /public_html/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.1 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 | -------------------------------------------------------------------------------- /public_html/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$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(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||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); -------------------------------------------------------------------------------- /public_html/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.1 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 | */ 10 | 11 | .clearfix { 12 | *zoom: 1; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | line-height: 0; 19 | content: ""; 20 | } 21 | 22 | .clearfix:after { 23 | clear: both; 24 | } 25 | 26 | .hide-text { 27 | font: 0/0 a; 28 | color: transparent; 29 | text-shadow: none; 30 | background-color: transparent; 31 | border: 0; 32 | } 33 | 34 | .input-block-level { 35 | display: block; 36 | width: 100%; 37 | min-height: 30px; 38 | -webkit-box-sizing: border-box; 39 | -moz-box-sizing: border-box; 40 | box-sizing: border-box; 41 | } 42 | 43 | @-ms-viewport { 44 | width: device-width; 45 | } 46 | 47 | .hidden { 48 | display: none; 49 | visibility: hidden; 50 | } 51 | 52 | .visible-phone { 53 | display: none !important; 54 | } 55 | 56 | .visible-tablet { 57 | display: none !important; 58 | } 59 | 60 | .hidden-desktop { 61 | display: none !important; 62 | } 63 | 64 | .visible-desktop { 65 | display: inherit !important; 66 | } 67 | 68 | @media (min-width: 768px) and (max-width: 979px) { 69 | .hidden-desktop { 70 | display: inherit !important; 71 | } 72 | .visible-desktop { 73 | display: none !important ; 74 | } 75 | .visible-tablet { 76 | display: inherit !important; 77 | } 78 | .hidden-tablet { 79 | display: none !important; 80 | } 81 | } 82 | 83 | @media (max-width: 767px) { 84 | .hidden-desktop { 85 | display: inherit !important; 86 | } 87 | .visible-desktop { 88 | display: none !important; 89 | } 90 | .visible-phone { 91 | display: inherit !important; 92 | } 93 | .hidden-phone { 94 | display: none !important; 95 | } 96 | } 97 | 98 | .visible-print { 99 | display: none !important; 100 | } 101 | 102 | @media print { 103 | .visible-print { 104 | display: inherit !important; 105 | } 106 | .hidden-print { 107 | display: none !important; 108 | } 109 | } 110 | 111 | @media (min-width: 1200px) { 112 | .row { 113 | margin-left: -30px; 114 | *zoom: 1; 115 | } 116 | .row:before, 117 | .row:after { 118 | display: table; 119 | line-height: 0; 120 | content: ""; 121 | } 122 | .row:after { 123 | clear: both; 124 | } 125 | [class*="span"] { 126 | float: left; 127 | min-height: 1px; 128 | margin-left: 30px; 129 | } 130 | .container, 131 | .navbar-static-top .container, 132 | .navbar-fixed-top .container, 133 | .navbar-fixed-bottom .container { 134 | width: 1170px; 135 | } 136 | .span12 { 137 | width: 1170px; 138 | } 139 | .span11 { 140 | width: 1070px; 141 | } 142 | .span10 { 143 | width: 970px; 144 | } 145 | .span9 { 146 | width: 870px; 147 | } 148 | .span8 { 149 | width: 770px; 150 | } 151 | .span7 { 152 | width: 670px; 153 | } 154 | .span6 { 155 | width: 570px; 156 | } 157 | .span5 { 158 | width: 470px; 159 | } 160 | .span4 { 161 | width: 370px; 162 | } 163 | .span3 { 164 | width: 270px; 165 | } 166 | .span2 { 167 | width: 170px; 168 | } 169 | .span1 { 170 | width: 70px; 171 | } 172 | .offset12 { 173 | margin-left: 1230px; 174 | } 175 | .offset11 { 176 | margin-left: 1130px; 177 | } 178 | .offset10 { 179 | margin-left: 1030px; 180 | } 181 | .offset9 { 182 | margin-left: 930px; 183 | } 184 | .offset8 { 185 | margin-left: 830px; 186 | } 187 | .offset7 { 188 | margin-left: 730px; 189 | } 190 | .offset6 { 191 | margin-left: 630px; 192 | } 193 | .offset5 { 194 | margin-left: 530px; 195 | } 196 | .offset4 { 197 | margin-left: 430px; 198 | } 199 | .offset3 { 200 | margin-left: 330px; 201 | } 202 | .offset2 { 203 | margin-left: 230px; 204 | } 205 | .offset1 { 206 | margin-left: 130px; 207 | } 208 | .row-fluid { 209 | width: 100%; 210 | *zoom: 1; 211 | } 212 | .row-fluid:before, 213 | .row-fluid:after { 214 | display: table; 215 | line-height: 0; 216 | content: ""; 217 | } 218 | .row-fluid:after { 219 | clear: both; 220 | } 221 | .row-fluid [class*="span"] { 222 | display: block; 223 | float: left; 224 | width: 100%; 225 | min-height: 30px; 226 | margin-left: 2.564102564102564%; 227 | *margin-left: 2.5109110747408616%; 228 | -webkit-box-sizing: border-box; 229 | -moz-box-sizing: border-box; 230 | box-sizing: border-box; 231 | } 232 | .row-fluid [class*="span"]:first-child { 233 | margin-left: 0; 234 | } 235 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 236 | margin-left: 2.564102564102564%; 237 | } 238 | .row-fluid .span12 { 239 | width: 100%; 240 | *width: 99.94680851063829%; 241 | } 242 | .row-fluid .span11 { 243 | width: 91.45299145299145%; 244 | *width: 91.39979996362975%; 245 | } 246 | .row-fluid .span10 { 247 | width: 82.90598290598291%; 248 | *width: 82.8527914166212%; 249 | } 250 | .row-fluid .span9 { 251 | width: 74.35897435897436%; 252 | *width: 74.30578286961266%; 253 | } 254 | .row-fluid .span8 { 255 | width: 65.81196581196582%; 256 | *width: 65.75877432260411%; 257 | } 258 | .row-fluid .span7 { 259 | width: 57.26495726495726%; 260 | *width: 57.21176577559556%; 261 | } 262 | .row-fluid .span6 { 263 | width: 48.717948717948715%; 264 | *width: 48.664757228587014%; 265 | } 266 | .row-fluid .span5 { 267 | width: 40.17094017094017%; 268 | *width: 40.11774868157847%; 269 | } 270 | .row-fluid .span4 { 271 | width: 31.623931623931625%; 272 | *width: 31.570740134569924%; 273 | } 274 | .row-fluid .span3 { 275 | width: 23.076923076923077%; 276 | *width: 23.023731587561375%; 277 | } 278 | .row-fluid .span2 { 279 | width: 14.52991452991453%; 280 | *width: 14.476723040552828%; 281 | } 282 | .row-fluid .span1 { 283 | width: 5.982905982905983%; 284 | *width: 5.929714493544281%; 285 | } 286 | .row-fluid .offset12 { 287 | margin-left: 105.12820512820512%; 288 | *margin-left: 105.02182214948171%; 289 | } 290 | .row-fluid .offset12:first-child { 291 | margin-left: 102.56410256410257%; 292 | *margin-left: 102.45771958537915%; 293 | } 294 | .row-fluid .offset11 { 295 | margin-left: 96.58119658119658%; 296 | *margin-left: 96.47481360247316%; 297 | } 298 | .row-fluid .offset11:first-child { 299 | margin-left: 94.01709401709402%; 300 | *margin-left: 93.91071103837061%; 301 | } 302 | .row-fluid .offset10 { 303 | margin-left: 88.03418803418803%; 304 | *margin-left: 87.92780505546462%; 305 | } 306 | .row-fluid .offset10:first-child { 307 | margin-left: 85.47008547008548%; 308 | *margin-left: 85.36370249136206%; 309 | } 310 | .row-fluid .offset9 { 311 | margin-left: 79.48717948717949%; 312 | *margin-left: 79.38079650845607%; 313 | } 314 | .row-fluid .offset9:first-child { 315 | margin-left: 76.92307692307693%; 316 | *margin-left: 76.81669394435352%; 317 | } 318 | .row-fluid .offset8 { 319 | margin-left: 70.94017094017094%; 320 | *margin-left: 70.83378796144753%; 321 | } 322 | .row-fluid .offset8:first-child { 323 | margin-left: 68.37606837606839%; 324 | *margin-left: 68.26968539734497%; 325 | } 326 | .row-fluid .offset7 { 327 | margin-left: 62.393162393162385%; 328 | *margin-left: 62.28677941443899%; 329 | } 330 | .row-fluid .offset7:first-child { 331 | margin-left: 59.82905982905982%; 332 | *margin-left: 59.72267685033642%; 333 | } 334 | .row-fluid .offset6 { 335 | margin-left: 53.84615384615384%; 336 | *margin-left: 53.739770867430444%; 337 | } 338 | .row-fluid .offset6:first-child { 339 | margin-left: 51.28205128205128%; 340 | *margin-left: 51.175668303327875%; 341 | } 342 | .row-fluid .offset5 { 343 | margin-left: 45.299145299145295%; 344 | *margin-left: 45.1927623204219%; 345 | } 346 | .row-fluid .offset5:first-child { 347 | margin-left: 42.73504273504273%; 348 | *margin-left: 42.62865975631933%; 349 | } 350 | .row-fluid .offset4 { 351 | margin-left: 36.75213675213675%; 352 | *margin-left: 36.645753773413354%; 353 | } 354 | .row-fluid .offset4:first-child { 355 | margin-left: 34.18803418803419%; 356 | *margin-left: 34.081651209310785%; 357 | } 358 | .row-fluid .offset3 { 359 | margin-left: 28.205128205128204%; 360 | *margin-left: 28.0987452264048%; 361 | } 362 | .row-fluid .offset3:first-child { 363 | margin-left: 25.641025641025642%; 364 | *margin-left: 25.53464266230224%; 365 | } 366 | .row-fluid .offset2 { 367 | margin-left: 19.65811965811966%; 368 | *margin-left: 19.551736679396257%; 369 | } 370 | .row-fluid .offset2:first-child { 371 | margin-left: 17.094017094017094%; 372 | *margin-left: 16.98763411529369%; 373 | } 374 | .row-fluid .offset1 { 375 | margin-left: 11.11111111111111%; 376 | *margin-left: 11.004728132387708%; 377 | } 378 | .row-fluid .offset1:first-child { 379 | margin-left: 8.547008547008547%; 380 | *margin-left: 8.440625568285142%; 381 | } 382 | input, 383 | textarea, 384 | .uneditable-input { 385 | margin-left: 0; 386 | } 387 | .controls-row [class*="span"] + [class*="span"] { 388 | margin-left: 30px; 389 | } 390 | input.span12, 391 | textarea.span12, 392 | .uneditable-input.span12 { 393 | width: 1156px; 394 | } 395 | input.span11, 396 | textarea.span11, 397 | .uneditable-input.span11 { 398 | width: 1056px; 399 | } 400 | input.span10, 401 | textarea.span10, 402 | .uneditable-input.span10 { 403 | width: 956px; 404 | } 405 | input.span9, 406 | textarea.span9, 407 | .uneditable-input.span9 { 408 | width: 856px; 409 | } 410 | input.span8, 411 | textarea.span8, 412 | .uneditable-input.span8 { 413 | width: 756px; 414 | } 415 | input.span7, 416 | textarea.span7, 417 | .uneditable-input.span7 { 418 | width: 656px; 419 | } 420 | input.span6, 421 | textarea.span6, 422 | .uneditable-input.span6 { 423 | width: 556px; 424 | } 425 | input.span5, 426 | textarea.span5, 427 | .uneditable-input.span5 { 428 | width: 456px; 429 | } 430 | input.span4, 431 | textarea.span4, 432 | .uneditable-input.span4 { 433 | width: 356px; 434 | } 435 | input.span3, 436 | textarea.span3, 437 | .uneditable-input.span3 { 438 | width: 256px; 439 | } 440 | input.span2, 441 | textarea.span2, 442 | .uneditable-input.span2 { 443 | width: 156px; 444 | } 445 | input.span1, 446 | textarea.span1, 447 | .uneditable-input.span1 { 448 | width: 56px; 449 | } 450 | .thumbnails { 451 | margin-left: -30px; 452 | } 453 | .thumbnails > li { 454 | margin-left: 30px; 455 | } 456 | .row-fluid .thumbnails { 457 | margin-left: 0; 458 | } 459 | } 460 | 461 | @media (min-width: 768px) and (max-width: 979px) { 462 | .row { 463 | margin-left: -20px; 464 | *zoom: 1; 465 | } 466 | .row:before, 467 | .row:after { 468 | display: table; 469 | line-height: 0; 470 | content: ""; 471 | } 472 | .row:after { 473 | clear: both; 474 | } 475 | [class*="span"] { 476 | float: left; 477 | min-height: 1px; 478 | margin-left: 20px; 479 | } 480 | .container, 481 | .navbar-static-top .container, 482 | .navbar-fixed-top .container, 483 | .navbar-fixed-bottom .container { 484 | width: 724px; 485 | } 486 | .span12 { 487 | width: 724px; 488 | } 489 | .span11 { 490 | width: 662px; 491 | } 492 | .span10 { 493 | width: 600px; 494 | } 495 | .span9 { 496 | width: 538px; 497 | } 498 | .span8 { 499 | width: 476px; 500 | } 501 | .span7 { 502 | width: 414px; 503 | } 504 | .span6 { 505 | width: 352px; 506 | } 507 | .span5 { 508 | width: 290px; 509 | } 510 | .span4 { 511 | width: 228px; 512 | } 513 | .span3 { 514 | width: 166px; 515 | } 516 | .span2 { 517 | width: 104px; 518 | } 519 | .span1 { 520 | width: 42px; 521 | } 522 | .offset12 { 523 | margin-left: 764px; 524 | } 525 | .offset11 { 526 | margin-left: 702px; 527 | } 528 | .offset10 { 529 | margin-left: 640px; 530 | } 531 | .offset9 { 532 | margin-left: 578px; 533 | } 534 | .offset8 { 535 | margin-left: 516px; 536 | } 537 | .offset7 { 538 | margin-left: 454px; 539 | } 540 | .offset6 { 541 | margin-left: 392px; 542 | } 543 | .offset5 { 544 | margin-left: 330px; 545 | } 546 | .offset4 { 547 | margin-left: 268px; 548 | } 549 | .offset3 { 550 | margin-left: 206px; 551 | } 552 | .offset2 { 553 | margin-left: 144px; 554 | } 555 | .offset1 { 556 | margin-left: 82px; 557 | } 558 | .row-fluid { 559 | width: 100%; 560 | *zoom: 1; 561 | } 562 | .row-fluid:before, 563 | .row-fluid:after { 564 | display: table; 565 | line-height: 0; 566 | content: ""; 567 | } 568 | .row-fluid:after { 569 | clear: both; 570 | } 571 | .row-fluid [class*="span"] { 572 | display: block; 573 | float: left; 574 | width: 100%; 575 | min-height: 30px; 576 | margin-left: 2.7624309392265194%; 577 | *margin-left: 2.709239449864817%; 578 | -webkit-box-sizing: border-box; 579 | -moz-box-sizing: border-box; 580 | box-sizing: border-box; 581 | } 582 | .row-fluid [class*="span"]:first-child { 583 | margin-left: 0; 584 | } 585 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 586 | margin-left: 2.7624309392265194%; 587 | } 588 | .row-fluid .span12 { 589 | width: 100%; 590 | *width: 99.94680851063829%; 591 | } 592 | .row-fluid .span11 { 593 | width: 91.43646408839778%; 594 | *width: 91.38327259903608%; 595 | } 596 | .row-fluid .span10 { 597 | width: 82.87292817679558%; 598 | *width: 82.81973668743387%; 599 | } 600 | .row-fluid .span9 { 601 | width: 74.30939226519337%; 602 | *width: 74.25620077583166%; 603 | } 604 | .row-fluid .span8 { 605 | width: 65.74585635359117%; 606 | *width: 65.69266486422946%; 607 | } 608 | .row-fluid .span7 { 609 | width: 57.18232044198895%; 610 | *width: 57.12912895262725%; 611 | } 612 | .row-fluid .span6 { 613 | width: 48.61878453038674%; 614 | *width: 48.56559304102504%; 615 | } 616 | .row-fluid .span5 { 617 | width: 40.05524861878453%; 618 | *width: 40.00205712942283%; 619 | } 620 | .row-fluid .span4 { 621 | width: 31.491712707182323%; 622 | *width: 31.43852121782062%; 623 | } 624 | .row-fluid .span3 { 625 | width: 22.92817679558011%; 626 | *width: 22.87498530621841%; 627 | } 628 | .row-fluid .span2 { 629 | width: 14.3646408839779%; 630 | *width: 14.311449394616199%; 631 | } 632 | .row-fluid .span1 { 633 | width: 5.801104972375691%; 634 | *width: 5.747913483013988%; 635 | } 636 | .row-fluid .offset12 { 637 | margin-left: 105.52486187845304%; 638 | *margin-left: 105.41847889972962%; 639 | } 640 | .row-fluid .offset12:first-child { 641 | margin-left: 102.76243093922652%; 642 | *margin-left: 102.6560479605031%; 643 | } 644 | .row-fluid .offset11 { 645 | margin-left: 96.96132596685082%; 646 | *margin-left: 96.8549429881274%; 647 | } 648 | .row-fluid .offset11:first-child { 649 | margin-left: 94.1988950276243%; 650 | *margin-left: 94.09251204890089%; 651 | } 652 | .row-fluid .offset10 { 653 | margin-left: 88.39779005524862%; 654 | *margin-left: 88.2914070765252%; 655 | } 656 | .row-fluid .offset10:first-child { 657 | margin-left: 85.6353591160221%; 658 | *margin-left: 85.52897613729868%; 659 | } 660 | .row-fluid .offset9 { 661 | margin-left: 79.8342541436464%; 662 | *margin-left: 79.72787116492299%; 663 | } 664 | .row-fluid .offset9:first-child { 665 | margin-left: 77.07182320441989%; 666 | *margin-left: 76.96544022569647%; 667 | } 668 | .row-fluid .offset8 { 669 | margin-left: 71.2707182320442%; 670 | *margin-left: 71.16433525332079%; 671 | } 672 | .row-fluid .offset8:first-child { 673 | margin-left: 68.50828729281768%; 674 | *margin-left: 68.40190431409427%; 675 | } 676 | .row-fluid .offset7 { 677 | margin-left: 62.70718232044199%; 678 | *margin-left: 62.600799341718584%; 679 | } 680 | .row-fluid .offset7:first-child { 681 | margin-left: 59.94475138121547%; 682 | *margin-left: 59.838368402492065%; 683 | } 684 | .row-fluid .offset6 { 685 | margin-left: 54.14364640883978%; 686 | *margin-left: 54.037263430116376%; 687 | } 688 | .row-fluid .offset6:first-child { 689 | margin-left: 51.38121546961326%; 690 | *margin-left: 51.27483249088986%; 691 | } 692 | .row-fluid .offset5 { 693 | margin-left: 45.58011049723757%; 694 | *margin-left: 45.47372751851417%; 695 | } 696 | .row-fluid .offset5:first-child { 697 | margin-left: 42.81767955801105%; 698 | *margin-left: 42.71129657928765%; 699 | } 700 | .row-fluid .offset4 { 701 | margin-left: 37.01657458563536%; 702 | *margin-left: 36.91019160691196%; 703 | } 704 | .row-fluid .offset4:first-child { 705 | margin-left: 34.25414364640884%; 706 | *margin-left: 34.14776066768544%; 707 | } 708 | .row-fluid .offset3 { 709 | margin-left: 28.45303867403315%; 710 | *margin-left: 28.346655695309746%; 711 | } 712 | .row-fluid .offset3:first-child { 713 | margin-left: 25.69060773480663%; 714 | *margin-left: 25.584224756083227%; 715 | } 716 | .row-fluid .offset2 { 717 | margin-left: 19.88950276243094%; 718 | *margin-left: 19.783119783707537%; 719 | } 720 | .row-fluid .offset2:first-child { 721 | margin-left: 17.12707182320442%; 722 | *margin-left: 17.02068884448102%; 723 | } 724 | .row-fluid .offset1 { 725 | margin-left: 11.32596685082873%; 726 | *margin-left: 11.219583872105325%; 727 | } 728 | .row-fluid .offset1:first-child { 729 | margin-left: 8.56353591160221%; 730 | *margin-left: 8.457152932878806%; 731 | } 732 | input, 733 | textarea, 734 | .uneditable-input { 735 | margin-left: 0; 736 | } 737 | .controls-row [class*="span"] + [class*="span"] { 738 | margin-left: 20px; 739 | } 740 | input.span12, 741 | textarea.span12, 742 | .uneditable-input.span12 { 743 | width: 710px; 744 | } 745 | input.span11, 746 | textarea.span11, 747 | .uneditable-input.span11 { 748 | width: 648px; 749 | } 750 | input.span10, 751 | textarea.span10, 752 | .uneditable-input.span10 { 753 | width: 586px; 754 | } 755 | input.span9, 756 | textarea.span9, 757 | .uneditable-input.span9 { 758 | width: 524px; 759 | } 760 | input.span8, 761 | textarea.span8, 762 | .uneditable-input.span8 { 763 | width: 462px; 764 | } 765 | input.span7, 766 | textarea.span7, 767 | .uneditable-input.span7 { 768 | width: 400px; 769 | } 770 | input.span6, 771 | textarea.span6, 772 | .uneditable-input.span6 { 773 | width: 338px; 774 | } 775 | input.span5, 776 | textarea.span5, 777 | .uneditable-input.span5 { 778 | width: 276px; 779 | } 780 | input.span4, 781 | textarea.span4, 782 | .uneditable-input.span4 { 783 | width: 214px; 784 | } 785 | input.span3, 786 | textarea.span3, 787 | .uneditable-input.span3 { 788 | width: 152px; 789 | } 790 | input.span2, 791 | textarea.span2, 792 | .uneditable-input.span2 { 793 | width: 90px; 794 | } 795 | input.span1, 796 | textarea.span1, 797 | .uneditable-input.span1 { 798 | width: 28px; 799 | } 800 | } 801 | 802 | @media (max-width: 767px) { 803 | body { 804 | padding-right: 20px; 805 | padding-left: 20px; 806 | } 807 | .navbar-fixed-top, 808 | .navbar-fixed-bottom, 809 | .navbar-static-top { 810 | margin-right: -20px; 811 | margin-left: -20px; 812 | } 813 | .container-fluid { 814 | padding: 0; 815 | } 816 | .dl-horizontal dt { 817 | float: none; 818 | width: auto; 819 | clear: none; 820 | text-align: left; 821 | } 822 | .dl-horizontal dd { 823 | margin-left: 0; 824 | } 825 | .container { 826 | width: auto; 827 | } 828 | .row-fluid { 829 | width: 100%; 830 | } 831 | .row, 832 | .thumbnails { 833 | margin-left: 0; 834 | } 835 | .thumbnails > li { 836 | float: none; 837 | margin-left: 0; 838 | } 839 | [class*="span"], 840 | .uneditable-input[class*="span"], 841 | .row-fluid [class*="span"] { 842 | display: block; 843 | float: none; 844 | width: 100%; 845 | margin-left: 0; 846 | -webkit-box-sizing: border-box; 847 | -moz-box-sizing: border-box; 848 | box-sizing: border-box; 849 | } 850 | .span12, 851 | .row-fluid .span12 { 852 | width: 100%; 853 | -webkit-box-sizing: border-box; 854 | -moz-box-sizing: border-box; 855 | box-sizing: border-box; 856 | } 857 | .row-fluid [class*="offset"]:first-child { 858 | margin-left: 0; 859 | } 860 | .input-large, 861 | .input-xlarge, 862 | .input-xxlarge, 863 | input[class*="span"], 864 | select[class*="span"], 865 | textarea[class*="span"], 866 | .uneditable-input { 867 | display: block; 868 | width: 100%; 869 | min-height: 30px; 870 | -webkit-box-sizing: border-box; 871 | -moz-box-sizing: border-box; 872 | box-sizing: border-box; 873 | } 874 | .input-prepend input, 875 | .input-append input, 876 | .input-prepend input[class*="span"], 877 | .input-append input[class*="span"] { 878 | display: inline-block; 879 | width: auto; 880 | } 881 | .controls-row [class*="span"] + [class*="span"] { 882 | margin-left: 0; 883 | } 884 | .modal { 885 | position: fixed; 886 | top: 20px; 887 | right: 20px; 888 | left: 20px; 889 | width: auto; 890 | margin: 0; 891 | } 892 | .modal.fade { 893 | top: -100px; 894 | } 895 | .modal.fade.in { 896 | top: 20px; 897 | } 898 | } 899 | 900 | @media (max-width: 480px) { 901 | .nav-collapse { 902 | -webkit-transform: translate3d(0, 0, 0); 903 | } 904 | .page-header h1 small { 905 | display: block; 906 | line-height: 20px; 907 | } 908 | input[type="checkbox"], 909 | input[type="radio"] { 910 | border: 1px solid #ccc; 911 | } 912 | .form-horizontal .control-label { 913 | float: none; 914 | width: auto; 915 | padding-top: 0; 916 | text-align: left; 917 | } 918 | .form-horizontal .controls { 919 | margin-left: 0; 920 | } 921 | .form-horizontal .control-list { 922 | padding-top: 0; 923 | } 924 | .form-horizontal .form-actions { 925 | padding-right: 10px; 926 | padding-left: 10px; 927 | } 928 | .media .pull-left, 929 | .media .pull-right { 930 | display: block; 931 | float: none; 932 | margin-bottom: 10px; 933 | } 934 | .media-object { 935 | margin-right: 0; 936 | margin-left: 0; 937 | } 938 | .modal { 939 | top: 10px; 940 | right: 10px; 941 | left: 10px; 942 | } 943 | .modal-header .close { 944 | padding: 10px; 945 | margin: -10px; 946 | } 947 | .carousel-caption { 948 | position: static; 949 | } 950 | } 951 | 952 | @media (max-width: 979px) { 953 | body { 954 | padding-top: 0; 955 | } 956 | .navbar-fixed-top, 957 | .navbar-fixed-bottom { 958 | position: static; 959 | } 960 | .navbar-fixed-top { 961 | margin-bottom: 20px; 962 | } 963 | .navbar-fixed-bottom { 964 | margin-top: 20px; 965 | } 966 | .navbar-fixed-top .navbar-inner, 967 | .navbar-fixed-bottom .navbar-inner { 968 | padding: 5px; 969 | } 970 | .navbar .container { 971 | width: auto; 972 | padding: 0; 973 | } 974 | .navbar .brand { 975 | padding-right: 10px; 976 | padding-left: 10px; 977 | margin: 0 0 0 -5px; 978 | } 979 | .nav-collapse { 980 | clear: both; 981 | } 982 | .nav-collapse .nav { 983 | float: none; 984 | margin: 0 0 10px; 985 | } 986 | .nav-collapse .nav > li { 987 | float: none; 988 | } 989 | .nav-collapse .nav > li > a { 990 | margin-bottom: 2px; 991 | } 992 | .nav-collapse .nav > .divider-vertical { 993 | display: none; 994 | } 995 | .nav-collapse .nav .nav-header { 996 | color: #777777; 997 | text-shadow: none; 998 | } 999 | .nav-collapse .nav > li > a, 1000 | .nav-collapse .dropdown-menu a { 1001 | padding: 9px 15px; 1002 | font-weight: bold; 1003 | color: #777777; 1004 | -webkit-border-radius: 3px; 1005 | -moz-border-radius: 3px; 1006 | border-radius: 3px; 1007 | } 1008 | .nav-collapse .btn { 1009 | padding: 4px 10px 4px; 1010 | font-weight: normal; 1011 | -webkit-border-radius: 4px; 1012 | -moz-border-radius: 4px; 1013 | border-radius: 4px; 1014 | } 1015 | .nav-collapse .dropdown-menu li + li a { 1016 | margin-bottom: 2px; 1017 | } 1018 | .nav-collapse .nav > li > a:hover, 1019 | .nav-collapse .nav > li > a:focus, 1020 | .nav-collapse .dropdown-menu a:hover, 1021 | .nav-collapse .dropdown-menu a:focus { 1022 | background-color: #f2f2f2; 1023 | } 1024 | .navbar-inverse .nav-collapse .nav > li > a, 1025 | .navbar-inverse .nav-collapse .dropdown-menu a { 1026 | color: #999999; 1027 | } 1028 | .navbar-inverse .nav-collapse .nav > li > a:hover, 1029 | .navbar-inverse .nav-collapse .nav > li > a:focus, 1030 | .navbar-inverse .nav-collapse .dropdown-menu a:hover, 1031 | .navbar-inverse .nav-collapse .dropdown-menu a:focus { 1032 | background-color: #111111; 1033 | } 1034 | .nav-collapse.in .btn-group { 1035 | padding: 0; 1036 | margin-top: 5px; 1037 | } 1038 | .nav-collapse .dropdown-menu { 1039 | position: static; 1040 | top: auto; 1041 | left: auto; 1042 | display: none; 1043 | float: none; 1044 | max-width: none; 1045 | padding: 0; 1046 | margin: 0 15px; 1047 | background-color: transparent; 1048 | border: none; 1049 | -webkit-border-radius: 0; 1050 | -moz-border-radius: 0; 1051 | border-radius: 0; 1052 | -webkit-box-shadow: none; 1053 | -moz-box-shadow: none; 1054 | box-shadow: none; 1055 | } 1056 | .nav-collapse .open > .dropdown-menu { 1057 | display: block; 1058 | } 1059 | .nav-collapse .dropdown-menu:before, 1060 | .nav-collapse .dropdown-menu:after { 1061 | display: none; 1062 | } 1063 | .nav-collapse .dropdown-menu .divider { 1064 | display: none; 1065 | } 1066 | .nav-collapse .nav > li > .dropdown-menu:before, 1067 | .nav-collapse .nav > li > .dropdown-menu:after { 1068 | display: none; 1069 | } 1070 | .nav-collapse .navbar-form, 1071 | .nav-collapse .navbar-search { 1072 | float: none; 1073 | padding: 10px 15px; 1074 | margin: 10px 0; 1075 | border-top: 1px solid #f2f2f2; 1076 | border-bottom: 1px solid #f2f2f2; 1077 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1078 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1079 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1080 | } 1081 | .navbar-inverse .nav-collapse .navbar-form, 1082 | .navbar-inverse .nav-collapse .navbar-search { 1083 | border-top-color: #111111; 1084 | border-bottom-color: #111111; 1085 | } 1086 | .navbar .nav-collapse .nav.pull-right { 1087 | float: none; 1088 | margin-left: 0; 1089 | } 1090 | .nav-collapse, 1091 | .nav-collapse.collapse { 1092 | height: 0; 1093 | overflow: hidden; 1094 | } 1095 | .navbar .btn-navbar { 1096 | display: block; 1097 | } 1098 | .navbar-static .navbar-inner { 1099 | padding-right: 10px; 1100 | padding-left: 10px; 1101 | } 1102 | } 1103 | 1104 | @media (min-width: 980px) { 1105 | .nav-collapse.collapse { 1106 | height: auto !important; 1107 | overflow: visible !important; 1108 | } 1109 | } 1110 | -------------------------------------------------------------------------------- /public_html/js/sparkline.js: -------------------------------------------------------------------------------- 1 | /* jquery.sparkline 2.1.1 - http://omnipotent.net/jquery.sparkline/ 2 | ** Licensed under the New BSD License - see above site for details */ 3 | 4 | (function(a){typeof define=="function"&&define.amd?define(["jquery"],a):a(jQuery)})(function(a){"use strict";var b={},c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I=0;c=function(){return{common:{type:"line",lineColor:"#00f",fillColor:"#cdf",defaultPixelsPerValue:3,width:"auto",height:"auto",composite:!1,tagValuesAttribute:"values",tagOptionsPrefix:"spark",enableTagOptions:!1,enableHighlight:!0,highlightLighten:1.4,tooltipSkipNull:!0,tooltipPrefix:"",tooltipSuffix:"",disableHiddenCheck:!1,numberFormatter:!1,numberDigitGroupCount:3,numberDigitGroupSep:",",numberDecimalMark:".",disableTooltips:!1,disableInteraction:!1},line:{spotColor:"#f80",highlightSpotColor:"#5f5",highlightLineColor:"#f22",spotRadius:1.5,minSpotColor:"#f80",maxSpotColor:"#f80",lineWidth:1,normalRangeMin:undefined,normalRangeMax:undefined,normalRangeColor:"#ccc",drawNormalOnTop:!1,chartRangeMin:undefined,chartRangeMax:undefined,chartRangeMinX:undefined,chartRangeMaxX:undefined,tooltipFormat:new e(' {{prefix}}{{y}}{{suffix}}')},bar:{barColor:"#3366cc",negBarColor:"#f44",stackedBarColor:["#3366cc","#dc3912","#ff9900","#109618","#66aa00","#dd4477","#0099c6","#990099"],zeroColor:undefined,nullColor:undefined,zeroAxis:!0,barWidth:4,barSpacing:1,chartRangeMax:undefined,chartRangeMin:undefined,chartRangeClip:!1,colorMap:undefined,tooltipFormat:new e(' {{prefix}}{{value}}{{suffix}}')},tristate:{barWidth:4,barSpacing:1,posBarColor:"#6f6",negBarColor:"#f44",zeroBarColor:"#999",colorMap:{},tooltipFormat:new e(' {{value:map}}'),tooltipValueLookups:{map:{"-1":"Loss",0:"Draw",1:"Win"}}},discrete:{lineHeight:"auto",thresholdColor:undefined,thresholdValue:0,chartRangeMax:undefined,chartRangeMin:undefined,chartRangeClip:!1,tooltipFormat:new e("{{prefix}}{{value}}{{suffix}}")},bullet:{targetColor:"#f33",targetWidth:3,performanceColor:"#33f",rangeColors:["#d3dafe","#a8b6ff","#7f94ff"],base:undefined,tooltipFormat:new e("{{fieldkey:fields}} - {{value}}"),tooltipValueLookups:{fields:{r:"Range",p:"Performance",t:"Target"}}},pie:{offset:0,sliceColors:["#3366cc","#dc3912","#ff9900","#109618","#66aa00","#dd4477","#0099c6","#990099"],borderWidth:0,borderColor:"#000",tooltipFormat:new e(' {{value}} ({{percent.1}}%)')},box:{raw:!1,boxLineColor:"#000",boxFillColor:"#cdf",whiskerColor:"#000",outlierLineColor:"#333",outlierFillColor:"#fff",medianColor:"#f00",showOutliers:!0,outlierIQR:1.5,spotRadius:1.5,target:undefined,targetColor:"#4a2",chartRangeMax:undefined,chartRangeMin:undefined,tooltipFormat:new e("{{field:fields}}: {{value}}"),tooltipFormatFieldlistKey:"field",tooltipValueLookups:{fields:{lq:"Lower Quartile",med:"Median",uq:"Upper Quartile",lo:"Left Outlier",ro:"Right Outlier",lw:"Left Whisker",rw:"Right Whisker"}}}}},B='.jqstooltip { position: absolute;left: 0px;top: 0px;visibility: hidden;background: rgb(0, 0, 0) transparent;background-color: rgba(0,0,0,0.6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";color: white;font: 10px arial, san serif;text-align: left;white-space: nowrap;padding: 5px;border: 1px solid white;z-index: 10000;}.jqsfield { color: white;font: 10px arial, san serif;text-align: left;}',d=function(){var b,c;return b=function(){this.init.apply(this,arguments)},arguments.length>1?(arguments[0]?(b.prototype=a.extend(new arguments[0],arguments[arguments.length-1]),b._super=arguments[0].prototype):b.prototype=arguments[arguments.length-1],arguments.length>2&&(c=Array.prototype.slice.call(arguments,1,-1),c.unshift(b.prototype),a.extend.apply(a,c))):b.prototype=arguments[0],b.prototype.cls=b,b},a.SPFormatClass=e=d({fre:/\{\{([\w.]+?)(:(.+?))?\}\}/g,precre:/(\w+)\.(\d+)/,init:function(a,b){this.format=a,this.fclass=b},render:function(a,b,c){var d=this,e=a,f,g,h,i,j;return this.format.replace(this.fre,function(){var a;return g=arguments[1],h=arguments[3],f=d.precre.exec(g),f?(j=f[2],g=f[1]):j=!1,i=e[g],i===undefined?"":h&&b&&b[h]?(a=b[h],a.get?b[h].get(i)||i:b[h][i]||i):(k(i)&&(c.get("numberFormatter")?i=c.get("numberFormatter")(i):i=p(i,j,c.get("numberDigitGroupCount"),c.get("numberDigitGroupSep"),c.get("numberDecimalMark"))),i)})}}),a.spformat=function(a,b){return new e(a,b)},f=function(a,b,c){return ac?c:a},g=function(a,b){var c;return b===2?(c=Math.floor(a.length/2),a.length%2?a[c]:(a[c-1]+a[c])/2):a.length%2?(c=(a.length*b+b)/4,c%1?(a[Math.floor(c)]+a[Math.floor(c)-1])/2:a[c-1]):(c=(a.length*b+2)/4,c%1?(a[Math.floor(c)]+a[Math.floor(c)-1])/2:a[c-1])},h=function(a){var b;switch(a){case"undefined":a=undefined;break;case"null":a=null;break;case"true":a=!0;break;case"false":a=!1;break;default:b=parseFloat(a),a==b&&(a=b)}return a},i=function(a){var b,c=[];for(b=a.length;b--;)c[b]=h(a[b]);return c},j=function(a,b){var c,d,e=[];for(c=0,d=a.length;c0;h-=d)b.splice(h,0,e);return b.join("")},l=function(a,b,c){var d;for(d=b.length;d--;){if(c&&b[d]===null)continue;if(b[d]!==a)return!1}return!0},m=function(a){var b=0,c;for(c=a.length;c--;)b+=typeof a[c]=="number"?a[c]:0;return b},o=function(b){return a.isArray(b)?b:[b]},n=function(a){var b;document.createStyleSheet?document.createStyleSheet().cssText=a:(b=document.createElement("style"),b.type="text/css",document.getElementsByTagName("head")[0].appendChild(b),b[typeof document.body.style.WebkitAppearance=="string"?"innerText":"innerHTML"]=a)},a.fn.simpledraw=function(b,c,d,e){var f,g;if(d&&(f=this.data("_jqs_vcanvas")))return f;b===undefined&&(b=a(this).innerWidth()),c===undefined&&(c=a(this).innerHeight());if(a.fn.sparkline.hasCanvas)f=new F(b,c,this,e);else{if(!a.fn.sparkline.hasVML)return!1;f=new G(b,c,this)}return g=a(this).data("_jqs_mhandler"),g&&g.registerCanvas(f),f},a.fn.cleardraw=function(){var a=this.data("_jqs_vcanvas");a&&a.reset()},a.RangeMapClass=q=d({init:function(a){var b,c,d=[];for(b in a)a.hasOwnProperty(b)&&typeof b=="string"&&b.indexOf(":")>-1&&(c=b.split(":"),c[0]=c[0].length===0?-Infinity:parseFloat(c[0]),c[1]=c[1].length===0?Infinity:parseFloat(c[1]),c[2]=a[b],d.push(c));this.map=a,this.rangelist=d||!1},get:function(a){var b=this.rangelist,c,d,e;if((e=this.map[a])!==undefined)return e;if(b)for(c=b.length;c--;){d=b[c];if(d[0]<=a&&d[1]>=a)return d[2]}return undefined}}),a.range_map=function(a){return new q(a)},r=d({init:function(b,c){var d=a(b);this.$el=d,this.options=c,this.currentPageX=0,this.currentPageY=0,this.el=b,this.splist=[],this.tooltip=null,this.over=!1,this.displayTooltips=!c.get("disableTooltips"),this.highlightEnabled=!c.get("disableHighlight")},registerSparkline:function(a){this.splist.push(a),this.over&&this.updateDisplay()},registerCanvas:function(b){var c=a(b.canvas);this.canvas=b,this.$canvas=c,c.mouseenter(a.proxy(this.mouseenter,this)),c.mouseleave(a.proxy(this.mouseleave,this)),c.click(a.proxy(this.mouseclick,this))},reset:function(a){this.splist=[],this.tooltip&&a&&(this.tooltip.remove(),this.tooltip=undefined)},mouseclick:function(b){var c=a.Event("sparklineClick");c.originalEvent=b,c.sparklines=this.splist,this.$el.trigger(c)},mouseenter:function(b){a(document.body).unbind("mousemove.jqs"),a(document.body).bind("mousemove.jqs",a.proxy(this.mousemove,this)),this.over=!0,this.currentPageX=b.pageX,this.currentPageY=b.pageY,this.currentEl=b.target,!this.tooltip&&this.displayTooltips&&(this.tooltip=new s(this.options),this.tooltip.updatePosition(b.pageX,b.pageY)),this.updateDisplay()},mouseleave:function(){a(document.body).unbind("mousemove.jqs");var b=this.splist,c=b.length,d=!1,e,f;this.over=!1,this.currentEl=null,this.tooltip&&(this.tooltip.remove(),this.tooltip=null);for(f=0;f