├── public
├── images
│ ├── favicon1.ico
│ ├── livechart.PNG
│ ├── maincharts.PNG
│ ├── livechart_filtered.PNG
│ └── recent_notifications.PNG
├── stylesheets
│ └── main.css
├── mainChart.html
├── indexPage.html
└── javascripts
│ ├── mainChart.js
│ ├── index.js
│ ├── chart.js
│ └── lib
│ ├── popper.min.js
│ ├── bootstrap.min.js
│ └── jquery-3.3.1.min.js
├── web.config
├── package.json
├── LICENSE
├── .gitignore
├── config
└── config.js
├── README.md
└── server.js
/public/images/favicon1.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AuthorizeNet/webhooks-sample-app/HEAD/public/images/favicon1.ico
--------------------------------------------------------------------------------
/public/images/livechart.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AuthorizeNet/webhooks-sample-app/HEAD/public/images/livechart.PNG
--------------------------------------------------------------------------------
/public/images/maincharts.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AuthorizeNet/webhooks-sample-app/HEAD/public/images/maincharts.PNG
--------------------------------------------------------------------------------
/public/images/livechart_filtered.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AuthorizeNet/webhooks-sample-app/HEAD/public/images/livechart_filtered.PNG
--------------------------------------------------------------------------------
/public/images/recent_notifications.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AuthorizeNet/webhooks-sample-app/HEAD/public/images/recent_notifications.PNG
--------------------------------------------------------------------------------
/public/stylesheets/main.css:
--------------------------------------------------------------------------------
1 | html {
2 | overflow-y: scroll;
3 | }
4 |
5 | .event-headings {
6 | width: 100%;
7 | margin: 0 auto;
8 | text-align: left;
9 | }
10 |
11 | .log-header {
12 | margin: 0 auto;
13 | text-align: center;
14 | }
15 |
16 | .display-none {
17 | display: none;
18 | }
19 |
20 | .dashboard-chart-row {
21 | width: 90%;
22 | margin: 0 auto;
23 | text-align: center;
24 | }
--------------------------------------------------------------------------------
/public/mainChart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhooks-sample-app",
3 | "version": "1.0.0",
4 | "description": "Webhook Dashboard to monitor real time events and recent business happenings",
5 | "main": "server.js",
6 | "dependencies": {
7 | "body-parser": "^1.18.3",
8 | "cookie-parser": "^1.4.3",
9 | "csurf": "^1.9.0",
10 | "ejs": "^2.6.1",
11 | "express": "^4.16.3",
12 | "helmet-csp": "^2.7.1",
13 | "http": "0.0.0",
14 | "lokijs": "^1.5.5",
15 | "request": "^2.87.0",
16 | "socket.io": "^2.1.1"
17 | },
18 | "devDependencies": {
19 | "nodemon": "^1.17.5",
20 | "jslint": "^0.12.0"
21 | },
22 | "scripts": {
23 | "test": "echo \"Error: no test specified\" && exit 1",
24 | "start": "node server"
25 | },
26 | "keywords": [
27 | "authorize.net",
28 | "webhooks"
29 | ],
30 | "author": "",
31 | "license": "ISC",
32 | "engines": {
33 | "node": "^8.11.2"
34 | },
35 | "repository": {
36 | "type": "git",
37 | "url": "https://github.com/AuthorizeNet/webhooks-sample-app.git"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Authorize.Net
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # database file
64 | *.db
65 |
--------------------------------------------------------------------------------
/config/config.js:
--------------------------------------------------------------------------------
1 | const env = 'dev'; // 'dev' or 'test'
2 |
3 | const dev = {
4 | apiEndpoint: 'https://apitest.authorize.net/rest/v1',
5 | apiLoginId: process.env.apiLogin || 'your api login id',
6 | transactionKey: process.env.transactionKey || 'your transaction key',
7 | app: {
8 | port: parseInt(process.env.PORT) || 9000,
9 | host: process.env.APP_DB_HOST || '0.0.0.0'
10 | },
11 | db: {
12 | name: process.env.DEV_DB_NAME || 'notification.db',
13 | size: 1000
14 | },
15 | graph: {
16 | // Number of Days to show in payment, refund, fraud and customer charts
17 | noOfDays: 7,
18 | maxNotificationCount: 100,
19 | // In seconds - interval between each time in X axis.
20 | intervalTimeSeconds: 300,
21 | graphTimeScale: 12 // Number of time intervals to show in graph
22 | }
23 | };
24 |
25 | const test = {
26 | apiEndpoint: 'https://apitest.authorize.net/rest/v1',
27 | apiLoginId: process.env.apiLogin || 'your api login id',
28 | transactionKey: process.env.transactionKey || 'your transaction key',
29 | app: {
30 | port: parseInt(process.env.PORT) || 9000,
31 | host: process.env.APP_DB_HOST || '0.0.0.0'
32 | },
33 | db: {
34 | name: process.env.DEV_DB_NAME || 'testnotification.db',
35 | size: 1000
36 | },
37 | graph: {
38 | // Number of Days to show in payment, refund, fraud and customer charts
39 | noOfDays: 7,
40 | maxNotificationCount: 100,
41 | // In seconds - interval between each time in X axis.
42 | intervalTimeSeconds: 300,
43 | graphTimeScale: 12 // Number of time intervals to show in graph
44 | }
45 | };
46 |
47 | const config = {
48 | dev,
49 | test
50 | };
51 |
52 | module.exports = config[env];
--------------------------------------------------------------------------------
/public/indexPage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Webhook Manager
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | | Timestamp |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
60 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webhooks Sample App
2 |
3 | Webhook sample application to demonstrate the usage of Webhooks to monitor real time events from Authorize.Net.
4 |
5 | * To learn more about Webhooks creation and configuration, authentication, API endpoints, event types and payload, visit [Webhooks](https://developer.authorize.net/api/reference/features/webhooks.html)
6 |
7 | * To get started with Authorize.Net, set up [Sandbox account](https://developer.authorize.net/hello_world/) and play.
8 |
9 | ## Screenshots
10 |
11 | #### Live Event Tracker
12 |
13 | Tracks live event occurrence and can be filtered based on the event types.
14 |
15 | 
16 |
17 | #### Recent Notifications
18 |
19 | Displays recent notifications. Can be filtered by event type using the drop down.
20 |
21 | 
22 |
23 | #### Dashboard Charts
24 |
25 | Payment, Refund, Customer and Fraud charts are displayed for past 7 days by default.
26 |
27 | 
28 |
29 | ## Register and configure Webhooks
30 |
31 | ### Prerequites
32 | * Authorize.NET production or sandbox credentials (api login id and transaction key)
33 |
34 | ### Steps to register a Webhook endpoint in Sandbox account
35 | * Login to [Sandbox](https://sandbox.authorize.net/) using sandbox account credentials.
36 |
37 | * Select the ACCOUNTS tab and click "Webhooks" link under Business Settings.
38 |
39 | * In Webhooks page, select "Add Endpoint" button.
40 |
41 | * Suppose this app is hosted as "https://my-webhooks-app.com" then enter "https://my-webhooks-app.com/notifications" in the Endpoint URL field.
42 |
43 | * Select the events for which you need to get notified in the app and click save button. Now the webhook is successfully configured to receive notifications.
44 |
45 |
46 | ## Installation
47 |
48 | Ensure that you have Node.js installed. node v8.11.2 and npm v5.6.0 are used for development. Please install the above or latest versions to use the app.
49 |
50 | The official Node.js website has installation instructions for Node.js: https://nodejs.org.
51 |
52 | Clone the Repository. Go to the project Directory and follow the below steps
53 |
54 | To install necessary packages, run following command in the terminal.
55 |
56 | ```
57 | npm install
58 | ```
59 | ## Usage
60 |
61 | In your terminal, run the following command:
62 |
63 | ```
64 | npm start
65 | ```
66 |
67 | The server is started at a port number (displayed in console) Eg. 9000.
68 | If you see no error message, navigate to `http://localhost:9000` in your browser.
69 |
70 | ## Config file
71 |
72 | config.js file in /config folder contains the following information:
73 |
74 | 1. API end point and credentials (required to populate the event types dropdown menu). In config/config.js file,
75 |
76 | ```
77 | apiEndpoint: 'https://apitest.authorize.net/rest/v1',
78 | apiLoginId: process.env.apiLogin || 'enter your api login id here',
79 | transactionKey: process.env.transactionKey || 'enter your transaction key here',
80 | ```
81 |
82 | 2. Server port number and hostname.
83 |
84 | ```
85 | port: parseInt(process.env.PORT) || 9000,
86 | host: process.env.APP_DB_HOST || '0.0.0.0'
87 | ```
88 |
89 | 3. Database name and size.
90 |
91 | ```
92 | name: process.env.DEV_DB_NAME || './db/notification.db',
93 | size: 1000
94 | ```
95 |
96 | 4. Graph Parameters:
97 |
98 | a) noOfDays: Number of days to plot in the graph for Payment, Refund, Customer and Fraud charts.
99 | Example: If noOfDays = 7:- Above 4 charts are shown for last 7 days.
100 |
101 | b) maxNotificationCount: Maximum number of recent notifications to display.
102 |
103 | c) intervalTimeSeconds: Interval time in seconds between each points in X axis for live event chart.
104 |
105 | d) graphTimeScale: Number of intervals to plot in live event chart.
106 |
107 | Example: If intervalTimeSeconds = 300 and graphTimeScale = 12:- live event chart is displayed for each 5 minutes for last one hour.
108 |
109 | ## Database File
110 |
111 | By default a database file to store recent notifications is created at /db/notification.db. Initially the live event chart is not displayed, notification log in UI does not contain any recent notifications and also empty charts are displayed in dashboard tab of the application since the server is just started and database is empty. When the server begins to receive notifications, charts and notification logs are updated.
112 |
113 | ## UI
114 |
115 | 1. Two tabs namely "Live Event Monitoring" and "Dashboard" are present.
116 | 2. First tab shows the Live Event chart at the top and live notification monitor at the bottom. A dropdown is present to filter the notifications by event type.
117 | 3. Second tab contains charts of Payment amount, Refund amount, number of Customers created and number of fraud transactions held in last few days.
118 |
119 |
120 |
--------------------------------------------------------------------------------
/public/javascripts/mainChart.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var chartColorIndex = 0, chartConfig;
3 |
4 | plotAllGraphs();
5 |
6 | /**
7 | * Calls the plotGraph() function with different chart types and DOM id for chart's location
8 | */
9 | function plotAllGraphs() {
10 | // console.log("in ploatallgraphs func - mainchart.js");
11 | plotGraph("Payment", "chart1");
12 | plotGraph("Refund", "chart2");
13 | plotGraph("Customer", "chart3");
14 | plotGraph("Fraud", "chart4");
15 | }
16 |
17 | /**
18 | * Make "/charts" call and get graph data
19 | * @param {string} eventCategory
20 | * @param {string} chartId
21 | */
22 | function plotGraph(eventCategory, chartId) {
23 | $.getJSON('/charts', { name: eventCategory }, function (resultsMap) {
24 | var results = resultsMap.data;
25 | if(Object.keys(results)) {
26 | chartConfig = {
27 | type: 'line',
28 | data: {
29 | // Extract the labels from results object
30 | labels: (function() {
31 | return Object.keys(results);
32 | }()),
33 |
34 | // Extract the values to plot from results object
35 | datasets: (function() {
36 | var datasetList = [], eventDataMap = {}, i, colorName, newColor;
37 | var nameList = Object.keys(results[Object.keys(results)[0]]);
38 |
39 | nameList.forEach((name) => {
40 | eventDataMap[name] = [];
41 | });
42 | Object.keys(results).forEach((graphDate) => {
43 | Object.keys(results[graphDate]).forEach((set) => {
44 | eventDataMap[set].push(results[graphDate][set]);
45 | });
46 | });
47 |
48 | for(i = 0; i < nameList.length; i += 1) {
49 | colorName = colorNames[chartColorIndex % colorNames.length];
50 | newColor = chartColors[colorName];
51 | ++chartColorIndex;
52 |
53 | datasetList.push({
54 | label: nameList[i],
55 | data: eventDataMap[nameList[i]],
56 | borderColor: newColor,
57 | backgroundColor: newColor,
58 | });
59 | }
60 | return datasetList;
61 | } ())
62 | },
63 | options: {
64 | tooltips: {
65 | callbacks: {
66 | // Change the tooltip display format
67 | label: function(tooltipItem, data) {
68 | var label = (data.datasets[tooltipItem.datasetIndex].label).split("=")[0] || '';
69 | var labelValue = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index] || 0;
70 | return (resultsMap.yaxis === "Amount")? ( label + ": $ " + labelValue): ( label + ": " + labelValue);
71 | }
72 | }
73 | },
74 | title: {
75 | display: true,
76 | text: eventCategory,
77 | fontSize: 20
78 | },
79 | legend:{
80 | position: "top",
81 | },
82 | scales: {
83 | xAxes: [{
84 | scaleLabel: {
85 | display: true,
86 | labelString: 'Date',
87 | fontStyle: "bold",
88 | fontSize: 14
89 | }
90 | }],
91 | yAxes : [{
92 | scaleLabel: {
93 | display: true,
94 | labelString: resultsMap.yaxis,
95 | fontStyle: "bold",
96 | fontSize: 14
97 | },
98 | ticks : {
99 | // Graph's Y-axis must start with zero
100 | min : 0,
101 | // Converts the Y-axis scale values to int if they are float.
102 | userCallback: function(label) {
103 | if (Math.floor(label) === label) {
104 | return label;
105 | }
106 | }
107 | }
108 | }]
109 | },
110 | animation: {
111 | duration: 0,
112 | },
113 | hover: {
114 | animationDuration: 0,
115 | },
116 | responsiveAnimationDuration: 0
117 | },
118 | plugins: [
119 | { // Change the way labels are displayed
120 | beforeInit: function(chartConfig) {
121 | chartConfig.data.datasets.forEach((dataset) => {
122 | var eventTotal = dataset.data.reduce((a,b) => a + b, 0);
123 | dataset.label = (resultsMap.yaxis === "Amount")? (dataset.label + " = $ " + eventTotal):
124 | (dataset.label + " = " + eventTotal);
125 | });
126 | },
127 | }
128 | ]
129 | };
130 | // Draw the chart with above data
131 | new Chart(document.getElementById(chartId).getContext("2d"), chartConfig);
132 | }
133 | });
134 | }
135 |
--------------------------------------------------------------------------------
/public/javascripts/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // Initialize socket variable
3 | var socket = io(), maxNotificationCount;
4 |
5 | /**
6 | * Triggered when an item is dropdown is selected.
7 | * Makes current panel content empty
8 | * Socket listens to capture the initial chart parameter
9 | * Calls filterNotificationLogByEventType() to filter the logs
10 | */
11 | $(document).on('click', '.dropdown-menu li a', function() {
12 | $(this).parents(".dropdown").find('.btn').html($(this).text() + ' ');
13 | $(this).parents(".dropdown").find('.btn').val($(this).data('value'));
14 | $("#panelCurrentEvent").html("");
15 |
16 | // Socket listens during initialization and gets the chart values from server
17 | if(!maxNotificationCount) {
18 | socket.on("init", (body) => {
19 | // console.log("in socket - index.js to get chart values ", body.noOfDaysGraph);
20 | maxNotificationCount = body.noOfDaysGraph;
21 | filterNotificationLogByEventType($(this).data('value'));
22 | });
23 | }
24 | else {
25 | filterNotificationLogByEventType($(this).data('value'));
26 | }
27 |
28 | });
29 |
30 | /**
31 | * Makes API call and gets the notification log based on filtered eventType
32 | * @param {string} eventType
33 | */
34 | function filterNotificationLogByEventType(eventType) {
35 | // console.log("eventtype selected in filterNotificationLogByEventType is ", eventType);
36 | $.getJSON("/notifications", { limit: maxNotificationCount, name: eventType }, function (notifications) {
37 | var notificationsLength = notifications.length;
38 | if(notificationsLength) {
39 | $("#logCount").html(`Displaying recent ${notificationsLength} notifications`);
40 | }
41 | else {
42 | $("#logCount").html(`No recent notifications`);
43 | }
44 | // Create a notification log in UI for each notification
45 | notifications.forEach((notification) =>
46 | displayEventMessage(notification));
47 | });
48 | }
49 |
50 | /**
51 | * Display either Event monitoring or dashboard of graphs
52 | * when the respective button is clicked
53 | */
54 | $(() => {
55 | // Set the CSRF headers
56 | $.ajaxSetup({
57 | headers: { 'x-csrf-token': $('input[name="_csrf"]').val() },
58 | data: { csrf: $('input[name="_csrf"]').val() }
59 | });
60 |
61 | // Get All eventTypes and construct the notification filtering dropdown menu
62 | $.ajax({
63 | method: "GET",
64 | url:"/eventtypes",
65 | dataType: "json",
66 | }).done(function(data) {
67 |
68 | // If error occurred in getting event types, error message is sent from server
69 | if(data.hasOwnProperty("message")) {
70 | $(".error-message").append(`${data.message}
\n`);
71 | }
72 | // console.log("adding dropdown items");
73 | else {
74 | data.forEach((eventName) => {
75 | var div_data = `${eventName.name}`;
76 | $(div_data).appendTo('.dropdown-menu');
77 | });
78 | }
79 |
80 | }).fail(function(err) {
81 | console.log("Error occured in ajax \"/events\" call ", err);
82 | });
83 |
84 | // Load the mainChart.html file that contains placeholder
85 | // payment, refund, customer and fraud charts
86 | $.ajax({
87 | url: '../mainChart.html',
88 | dataType: 'html',
89 | timeout: 5000, // 5 seconds
90 | success: function(html) {
91 | $("#includedContent").html(html);
92 | }
93 | });
94 |
95 | // Include the mainChart.js file that has function for dashboard charts
96 | $.getScript("./javascripts/mainChart.js");
97 |
98 | // By default enable the Event Monitoring tab
99 | $("#currentEvents").css("background-color", "palevioletred");
100 |
101 | $("#chart").click(() => {
102 | // Change Dashboard tab's background color
103 | $("#chart").css("background-color", "palevioletred");
104 | // Reset Event Monitoring tab's background color
105 | $("#currentEvents").css("background-color", "");
106 | // Hide the Event Monitoring tab's content
107 | $(".event-monitoring").css("display", "none");
108 | // Display the Dashboard tab's content
109 | $("#includedContent").css("display", "block");
110 | });
111 |
112 | $("#currentEvents").click(() => {
113 | // Change Event Monitoring tab's background color
114 | $("#currentEvents").css("background-color", "palevioletred");
115 | // Reset Dashboard tab's background color
116 | $("#chart").css("background-color", "");
117 | // Hide the Dashboard tab's content
118 | $("#includedContent").css("display", "none");
119 | // Display the Event Monitoring tab's content
120 | $(".event-monitoring").css("display", "block");
121 | });
122 | // // console.log("clicking all items by defaut");
123 | $('.dropdown li a:first-child').click();
124 |
125 | });
126 |
127 | // Socket listens for new event that is emitted in the server side.
128 | // Calls onNewEvent method when a new event occurs and updates the graph
129 | socket.on("newNotification", (body) => {
130 | console.log("in socket - index.js");
131 | onNewEvent(body);
132 | plotAllGraphs();
133 | });
134 |
135 | /**
136 | * Formats the event date into only date time format
137 | * Converting "2018-07-21T20:52:59.2144524Z" to "2018-07-21 20:52:59"
138 | * @param {string} eDate
139 | */
140 | function formatEventDate(eDate) {
141 | var eventDate = eDate.split("T");
142 | eventDate[1] = eventDate[1].split(".")[0];
143 | eventDate = eventDate.join(" ");
144 | return eventDate;
145 | }
146 |
147 | /**
148 | * Called when new notification is received. Calls methods to display the event
149 | * log message and update event frequency in event log graph
150 | * @param {*} body
151 | */
152 | function onNewEvent(body) {
153 | console.log("in onNewEvent func");
154 | // var eventName = extracteventNameFromEventType(body.eventDetails.eventType);
155 | displayEventMessage(body.eventDetails);
156 | findEventFrequencyInGraphInterval(body.eventDetails.eventType);
157 | }
158 |
159 | /**
160 | * Creates a new event message with event timestamp, event type and payload.
161 | * Appends this message to an UI element adds animation to it
162 | * @param {*} eventDetails
163 | */
164 | function displayEventMessage(eventDetails) {
165 | // console.log("in displayEventMessage func");
166 | var eventDate = formatEventDate(eventDetails.eventDate);
167 | var mainPanel = document.getElementById("panelCurrentEvent");
168 |
169 | // Checks the limit to display number of recent events
170 | if(mainPanel.childElementCount >= maxNotificationCount) {
171 | mainPanel.removeChild(mainPanel.lastChild);
172 | }
173 | var newPanel = document.createElement("div");
174 | newPanel.classList.add("panel", "panel-success");
175 |
176 | var formatedPayload = JSON.stringify(eventDetails, null,"\t")
177 | .slice(1,-1);
178 |
179 | newPanel.innerHTML =
180 | `
181 |
${eventDate}
182 |
${eventDetails.eventType}
183 |
184 |
${formatedPayload}
185 |
186 |
`;
187 |
188 | // For animation
189 | newPanel.animate([
190 | // keyframes
191 | { transform: "translateX(300px)" },
192 | { transform: "translateY(0px)" }
193 | ], {
194 | // timing options
195 | duration: 500,
196 | });
197 | // Insert message as the first child
198 | mainPanel.insertBefore(newPanel, mainPanel.childNodes[0]);
199 | }
200 |
--------------------------------------------------------------------------------
/public/javascripts/chart.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var chartColors = {
3 | Yellow: 'rgb(255, 225, 25)',
4 | Blue: 'rgb(0, 130, 200)',
5 | Orange: 'rgb(245, 130, 48)',
6 | Purple: 'rgb(145, 30, 180)',
7 | Cyan: 'rgb(70, 240, 240)',
8 | Magenta: 'rgb(240, 50, 230)',
9 | Lime: 'rgb(210, 245, 60)',
10 | Pink: 'rgb(250, 190, 190)',
11 | Teal: 'rgb(0, 128, 128)',
12 | Lavender: 'rgb(230, 190, 255)',
13 | Red: 'rgb(230, 25, 75)',
14 | Brown: 'rgb(170, 110, 40)',
15 | Beige: 'rgb(255, 250, 200)',
16 | Maroon: 'rgb(128, 0, 0)',
17 | Mint: 'rgb(170, 255, 195)',
18 | Olive: 'rgb(128, 128, 0)',
19 | Coral: 'rgb(255, 215, 180)',
20 | Navy: 'rgb(0, 0, 128)',
21 | Green: 'rgb(60, 180, 75)',
22 | Grey: 'rgb(128, 128, 128)',
23 | Black: 'rgb(0, 0, 0)',
24 | };
25 |
26 | var config,
27 | initialChartData,
28 | eventFrequencyInGraphInterval = {},
29 | myLine,
30 | colorNames = Object.keys(chartColors);
31 |
32 | /**
33 | * Initialize chart data and draws chart if the chart data is populated
34 | * @param {*} chartResults
35 | */
36 | function initialChartCallback(chartResults) {
37 | initialChartData = chartResults;
38 |
39 | if(Object.keys(initialChartData.eventFrequencyAtEachTimeMap).length!= 0) {
40 | drawChart(initialChartData);
41 | }
42 | }
43 | initializeChart();
44 |
45 | /**
46 | * Gets chart values from "/charts" endpoint.
47 | * calls initialChartCallback() with chart data
48 | */
49 | function initializeChart() {
50 | // For live event chart. Query parameter name is passed with value "all"
51 | $.getJSON('/charts', { name: "all", csrf: $('input[name="_csrf"]').val() }, (results) => { initialChartCallback(results); });
52 | }
53 |
54 | /**
55 | * Initializes chart configurations. Plots chart and calls function to
56 | * update chart regularly
57 | * @param {*} results
58 | */
59 | function drawChart(results) {
60 | try{
61 | config = {
62 | type: "line",
63 | data: {
64 | // Extract the labels from results object
65 | labels: (function() {
66 | var timeLabelList = [];
67 | for(var i=0; i {
145 | var eventCount = dataset.data.reduce((a,b) => a + b, 0);
146 | dataset.label = dataset.label + " ("+ eventCount + ")";
147 | // Hide the event if its count is zero in this timeframe
148 | if(!eventCount) {
149 | dataset.hidden = true;
150 | }
151 | });
152 | }
153 | },
154 | {
155 | // After Updating chart, remove the count from event label
156 | afterUpdate: function(config) {
157 | config.data.datasets.forEach((dataset) => {
158 | dataset.label = dataset.label.split(" ")[0];
159 | if(dataset.hidden) {
160 | dataset.hidden = false;
161 | }
162 | });
163 | }
164 | }
165 | ]
166 | };
167 | }catch(err) {
168 | console.error("Error in chart.js ", err);
169 | }
170 |
171 | // Plot the chart
172 | $(document).ready(function() {
173 | var ctx = document.getElementById("chartBox").getContext("2d");
174 | myLine = new Chart(ctx, config);
175 | });
176 |
177 | // Execute the setInterval's callback function repeatedly
178 | var stopGraphUpdate = setInterval(function () {
179 | updateLiveEventGraph(results.intervalTimeSeconds, eventFrequencyInGraphInterval);
180 | eventFrequencyInGraphInterval = {};
181 | }, results.intervalTimeSeconds * 1000);
182 | }
183 |
184 | /**
185 | * During each call, updates the X-axis, adds new event if occurred,
186 | * updates existing event whether occurred in this time interval or not
187 | * @param {number} intervalTimeSeconds
188 | * @param {*} eventFrequencyInGraphInterval
189 | */
190 | function updateLiveEventGraph(intervalTimeSeconds, eventFrequencyInGraphInterval) {
191 | if(config.data.datasets) {
192 | updateXAxis(intervalTimeSeconds);
193 | }
194 |
195 | config.data.datasets.forEach((dataset)=> {
196 | var updatedFrequencyFlag = false;
197 | Object.keys(eventFrequencyInGraphInterval).forEach(function(newEvent) {
198 |
199 | // If already present event has occurred in this timeframe,
200 | // update with new count
201 | if(newEvent === dataset.label) {
202 | // console.log("If already present event has occurred in this timeframe, update with new count")
203 | addData(dataset.label, eventFrequencyInGraphInterval[newEvent]);
204 | updatedFrequencyFlag = true;
205 | }
206 | });
207 |
208 | // If already present event does not occur in the time frame, add "0" as count
209 | if(!updatedFrequencyFlag) {
210 | // console.log("If already present event does not occur in the time frame, add 0 as count")
211 | addData(dataset.label, 0);
212 | }
213 | });
214 |
215 | Object.keys(eventFrequencyInGraphInterval).forEach(function(newEvent) {
216 | var updatedFrequency1Flag = false;
217 | config.data.datasets.forEach(function(dataset) {
218 | if(newEvent === dataset.label) {
219 | updatedFrequency1Flag = true;
220 | }
221 | });
222 |
223 | // If new event has occurred, add a dataset for it and update its latest count
224 | if(!updatedFrequency1Flag) {
225 | // console.log("If new event has occurred, add a dataset for it and update its latest count")
226 | addDataset(newEvent);
227 | addData(newEvent, eventFrequencyInGraphInterval[newEvent]);
228 | }
229 | });
230 | // Update the graph after making necessary changes
231 | myLine.update();
232 | }
233 |
234 | /**
235 | * Add a dataset for newly occurred event
236 | * @param {*} newEvent
237 | */
238 | function addDataset(newEvent) {
239 | // console.log("new series adding for event ", newEvent);
240 | var colorName = colorNames[config.data.datasets.length % colorNames.length];
241 | var newColor = chartColors[colorName];
242 | var newDataset = {
243 | label: newEvent,
244 | data: [],
245 | borderColor: newColor,
246 | backgroundColor: newColor,
247 | };
248 |
249 | // Make count as zero for each value in X axis
250 | for (var index = 0; index < config.data.labels.length; ++index)
251 | newDataset.data.push(0);
252 |
253 | config.data.datasets.push(newDataset);
254 | }
255 |
256 | /**
257 | * Add a data point for already existing event or new event
258 | * @param {string} datasetName
259 | * @param {number} value
260 | */
261 | function addData(datasetName, value) {
262 | if (config.data.datasets.length > 0 && datasetName!= undefined) {
263 | config.data.datasets.forEach(function(dataset, index, chartSeriesList) {
264 | if(dataset.label === datasetName) {
265 | chartSeriesList[index].data.push(value);
266 | chartSeriesList[index].data.shift();
267 | }
268 | });
269 | }
270 | }
271 |
272 | /**
273 | * Updates the X axis by adding new time and removing the earliest time
274 | * @param {number} intervalTimeSeconds
275 | */
276 | function updateXAxis(intervalTimeSeconds) {
277 | var latestTime = config.data.labels[config.data.labels.length-1];
278 | var nextTime = new Date(new Date(latestTime).getTime() + (1000 * intervalTimeSeconds)).toISOString();
279 | config.data.labels.push(nextTime);
280 | config.data.labels.shift();
281 | }
282 |
283 | /**
284 | * Maintains the event occurrence count for each interval
285 | * @param {string} eventType
286 | */
287 | function findEventFrequencyInGraphInterval(eventType) {
288 | if(Object.keys(initialChartData.eventFrequencyAtEachTimeMap).length == 0) {
289 | initializeChart();
290 | }
291 | else {
292 | if(eventType in eventFrequencyInGraphInterval)
293 | eventFrequencyInGraphInterval[eventType] += 1;
294 | else
295 | eventFrequencyInGraphInterval[eventType] = 1;
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/public/javascripts/lib/popper.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) Federico Zivolo 2018
3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=J(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!q(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,y=t(e.instance.popper),w=parseFloat(y['margin'+f],10),E=parseFloat(y['border'+f+'Width'],10),v=b-e.offsets.popper[m]-w-E;return v=$(J(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,Q(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case he.FLIP:p=[n,i];break;case he.CLOCKWISE:p=z(n);break;case he.COUNTERCLOCKWISE:p=z(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,y=-1!==['top','bottom'].indexOf(n),w=!!t.flipVariations&&(y&&'start'===r&&h||y&&'end'===r&&c||!y&&'start'===r&&g||!y&&'end'===r&&u);(m||b||w)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),w&&(r=G(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!q(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right 0) {
158 | var someDate = new Date(new Date().setDate(new Date().getDate()- x + 1)).toISOString().slice(0,10).split('-');
159 | tempDate = someDate[1] +'-'+ someDate[2] +'-'+ someDate[0];
160 | recentDateMap[tempDate] = {};
161 | --x;
162 | }
163 | return recentDateMap;
164 | }
165 |
166 | /**
167 | * Filter based on event list from DB
168 | * @param {*} notifications
169 | * @param {Array} eventFilter
170 | */
171 | async function getMatchingEventsFromDB(notifications, eventFilter, offsetValue, limitValue) {
172 |
173 | if(offsetValue === -1 && limitValue === -1) {
174 | return notifications.chain().find({eventType: { $in: eventFilter } }).data({removeMeta: true});
175 | }
176 | else {
177 | if(eventFilter.length!= 0) {
178 | return notifications.chain().find({eventType: { $in: eventFilter } }).offset(offsetValue).limit(limitValue).data({removeMeta: true});
179 | }
180 |
181 | else {
182 | return notifications.chain().offset(offsetValue).limit(limitValue).data({removeMeta: true});
183 | }
184 | }
185 | }
186 |
187 | /**
188 | * Calculates the date range and values to plot in chart based on input parameters
189 | * @param {Array} eventFilter
190 | * @param {Array} eventKeyList
191 | * @param {string} calculateParameter
192 | */
193 | async function getGraphData(eventFilter, eventKeyList, calculateParameter) {
194 | var recentDateMap = calculateLastXDays(noOfDaysGraph);
195 |
196 | var notifications = await getCollectionFunction("notifications");
197 |
198 | var recentDocs = await getMatchingEventsFromDB(notifications, eventFilter, -1, -1);
199 |
200 | // If recentDocs is null, the charts are displayed with all values as zero
201 | // for each date in recentDateMap
202 | if(!(eventKeyList === undefined || eventKeyList.length === 0)) {
203 | eventKeyList.forEach((eventKey) => {
204 | Object.keys(recentDateMap).forEach((eachDate) => {
205 | recentDateMap[eachDate][eventKey] = 0;
206 | });
207 | });
208 | }
209 |
210 | recentDocs.forEach( (element) => {
211 | var tempDate = element.eventDate.slice(0, 10).split('-');
212 | var elementDate = tempDate[1] +'-'+ tempDate[2] +'-'+ tempDate[0];
213 |
214 | if(elementDate in recentDateMap) {
215 | Object.keys(recentDateMap[elementDate]).forEach((eventLegend) => {
216 | if(eventKeyList.includes(eventLegend)) {
217 | if(calculateParameter === "Amount") {
218 | recentDateMap[elementDate][eventLegend] += parseInt(element.payload.authAmount);
219 | }
220 | else if(calculateParameter === "Count") {
221 | recentDateMap[elementDate][eventLegend] += 1;
222 | }
223 | }
224 | else {
225 | recentDateMap[elementDate][eventLegend] = 0;
226 | }
227 | });
228 | }
229 |
230 | });
231 | var chartMap = {
232 | "data": recentDateMap,
233 | "yaxis": calculateParameter
234 | };
235 | return chartMap;
236 | }
237 |
238 | /**
239 | * Define what to display in the chart and call getGraphData() with it
240 | * @param {string} eventCategory
241 | */
242 | async function setGraphCriteria(eventCategory) {
243 | var eventFilter, eventKeyList, calculateParameter;
244 | switch(eventCategory) {
245 | case "Payment":
246 | eventFilter = [
247 | "net.authorize.payment.authcapture.created",
248 | "net.authorize.payment.priorAuthCapture.created",
249 | "net.authorize.payment.capture.created"
250 | ];
251 | // eventKeyList = ["Total Amount", "second dataset"];
252 | eventKeyList = ["Total Payment Amount"];
253 | calculateParameter = "Amount";
254 | break;
255 |
256 | case "Refund":
257 | eventFilter = [
258 | "net.authorize.payment.refund.created",
259 | "net.authorize.payment.void.created"
260 | ];
261 | eventKeyList = ["Total Refund Amount"];
262 | calculateParameter = "Amount";
263 | break;
264 |
265 | case "Customer":
266 | eventFilter = [
267 | "net.authorize.customer.created"
268 | ];
269 | eventKeyList = ["# of Customer Profile created"];
270 | calculateParameter = "Count";
271 | break;
272 |
273 | case "Fraud":
274 | eventFilter = [
275 | "net.authorize.payment.fraud.held"
276 | ];
277 | eventKeyList = ["# of Fraud transactions held"];
278 | calculateParameter = "Count";
279 | break;
280 | }
281 |
282 | var recentDateMap = await getGraphData(eventFilter, eventKeyList, calculateParameter);
283 | return recentDateMap;
284 | }
285 |
286 | /**
287 | * Returns the oldest notification present
288 | * @param {*} notifications
289 | */
290 | async function getOldestNotification(notifications) {
291 | return notifications.chain().limit(1).data();
292 | }
293 |
294 | /**
295 | * Removes the oldest notification from available set of notifications
296 | * @param {*} notifications
297 | * @param {*} oldestNotification
298 | */
299 | async function removeOldestNotification(notifications, oldestNotification) {
300 | notifications.remove(oldestNotification);
301 | return notifications;
302 | }
303 |
304 | /**
305 | * Retrieves and Deletes the oldest notification present in the set of notifications
306 | * @param {*} notifications
307 | */
308 | async function handleOldestNotification(notifications) {
309 | if (notifications.count() >= dbSize) {
310 | var oldestNotification = await getOldestNotification(notifications);
311 | notifications = await removeOldestNotification(notifications,oldestNotification);
312 | return notifications;
313 | }
314 | else {
315 | return notifications;
316 | }
317 | }
318 |
319 | /**
320 | * Initializes current set of notifications and removes the oldest one from it
321 | */
322 | async function updateNotifications() {
323 | var notifications = await getCollectionFunction("notifications");
324 |
325 | notifications = await handleOldestNotification(notifications);
326 |
327 | return notifications;
328 | }
329 |
330 | /**
331 | * Insert incoming new notification into available notifications set and returns the new set
332 | * @param {*} notifications
333 | * @param {*} newNotification
334 | */
335 | async function insertNewNotification(notifications, newNotification) {
336 | notifications.insert(newNotification);
337 | return notifications;
338 | }
339 |
340 | /**
341 | * Finds the time values to be plotted in live event chart and returns as an Array
342 | * @param {Date} startTime
343 | */
344 | function calculateEventGraphInterval(startTime) {
345 | // console.log("start time: ", startTime);
346 | var newTime = new Date(startTime.getTime() + (1000 * config.graph.intervalTimeSeconds));
347 | var graphTimeList = [], currentTime = new Date(), tempDate;
348 | // console.log("currentTime", currentTime);
349 | tempDate = startTime.toISOString().slice(0, 24);
350 | graphTimeList.push(startTime);
351 |
352 | while(newTime <= currentTime) {
353 | tempDate = newTime.toISOString().slice(0, 24);
354 | graphTimeList.push(newTime);
355 | newTime = new Date(newTime.getTime() + (1000 * config.graph.intervalTimeSeconds));
356 | }
357 | return graphTimeList;
358 | }
359 |
360 | /**
361 | * Gets the collection from database
362 | * @param {string} collectionName
363 | */
364 | async function getCollectionFunction(collectionName) {
365 | return db.getCollection(collectionName);
366 | }
367 |
368 | /**
369 | * Returns the mapping of eventType and it count of occurrence
370 | * @param {Array} graphTimeList
371 | * @param {*} graphEvents
372 | */
373 | function findEventsFrequencyInGraphInterval(graphTimeList, graphEvents) {
374 | var eventFrequencyAtEachTimeMap = {}; // {"event1": [4,0,3,1,0,6,2,3,0,0], "event2": [0,0,5,3,2,0,5,7,8,9]}
375 | if(graphEvents) {
376 | graphEvents.forEach(function(event) {
377 | var timeDiff = Math.abs(new Date(event.eventDate).getTime() - new Date(graphTimeList[0]).getTime());
378 | var index = Math.ceil(timeDiff/ (config.graph.intervalTimeSeconds * 1000));
379 |
380 | if (eventFrequencyAtEachTimeMap[event.eventType] === undefined || eventFrequencyAtEachTimeMap[event.eventType].length == 0) {
381 | eventFrequencyAtEachTimeMap[event.eventType] = new Array(config.graph.graphTimeScale).fill(0);
382 | eventFrequencyAtEachTimeMap[event.eventType][index-1] = 1;
383 | }
384 | else {
385 | eventFrequencyAtEachTimeMap[event.eventType][index-1] += 1;
386 | }
387 | });
388 | }
389 | else {
390 | console.log("no events occured in requested interval");
391 | }
392 | return eventFrequencyAtEachTimeMap;
393 | }
394 |
395 | /**
396 | * Returns the notifications occurred after the graph start time
397 | * @param {*} notifications
398 | * @param {Date} graphStartTime
399 | */
400 | async function filterToGetGraphEvents(notifications, graphStartTime) {
401 | return notifications.chain().where(function(obj) {
402 | return graphStartTime < new Date(obj.eventDate)}).data({removeMeta: true});
403 | }
404 |
405 | /**
406 | * Returns the data required for live event chart
407 | */
408 | async function getAllEventsChart() {
409 | var currentTime = new Date();
410 | var calculateFromTime = new Date(new Date().setTime(currentTime.getTime()- (1000 * config.graph.intervalTimeSeconds * config.graph.graphTimeScale)));
411 | var graphStartTime = new Date(calculateFromTime.getTime() + (1000 * config.graph.intervalTimeSeconds));
412 |
413 | var graphTimeList = calculateEventGraphInterval(calculateFromTime);
414 | try {
415 | var notifications = await getCollectionFunction("notifications");
416 |
417 | var graphEvents = await filterToGetGraphEvents(notifications, graphStartTime);
418 |
419 | var eventFrequencyAtEachTimeMap = findEventsFrequencyInGraphInterval(graphTimeList, graphEvents);
420 |
421 | return {
422 | eventFrequencyAtEachTimeMap: eventFrequencyAtEachTimeMap,
423 | graphStartTime: graphTimeList[1],
424 | intervalTimeSeconds: config.graph.intervalTimeSeconds
425 | };
426 | }catch(err) {
427 | console.error("Error happened in getting graphEvents ", err);
428 | return {};
429 | }
430 | }
431 |
432 | /**
433 | * Returns the count of collection
434 | * @param {string} collectionName
435 | */
436 | async function findCollectionsCount(collectionName) {
437 | return collectionName.count()
438 | }
439 |
440 | /**
441 | * Returns recent notifications by filtering from available set
442 | * @param {*} notifications
443 | * @param {*} limit
444 | */
445 | async function filterToGetRecentNotifications(notifications, limit, eventType) {
446 |
447 | var collectionCount = await findCollectionsCount(notifications);
448 |
449 | var offset = 0, eventFilter = [], limitValue = limit;
450 | if(eventType === "all") {
451 | if(collectionCount > limitValue) {
452 | offset = collectionCount - limitValue;
453 | }
454 | else {
455 | limitValue = collectionCount;
456 | }
457 | }
458 |
459 | else {
460 | limitValue = collectionCount;
461 | eventFilter = [eventType];
462 | }
463 |
464 | var recentNotifications = await getMatchingEventsFromDB(notifications, eventFilter, offset, limitValue);
465 |
466 | if(eventType!= "all") {
467 | if(recentNotifications.length > limit) {
468 | recentNotifications.splice(0, recentNotifications.length - limit);
469 | }
470 | }
471 |
472 | return recentNotifications;
473 | }
474 |
475 | /**
476 | * Calls filterToGetRecentNotifications() to get the required recent notifications and returns it
477 | * @param {number} limit
478 | */
479 | async function getRecentNotifications(limit, eventType) {
480 | try {
481 | var notifications = await getCollectionFunction("notifications");
482 |
483 | var recentNotifications = await filterToGetRecentNotifications(notifications, limit, eventType);
484 |
485 | return recentNotifications;
486 |
487 | }catch(err) {
488 | console.error("Error happened in getting recent notifications ", err);
489 | return [];
490 | }
491 | }
492 |
493 | // Endpoint to return recent requested number of notifications
494 | app.get("/notifications", csrfProtection, async function(req, res) {
495 | var recentNotifications = await getRecentNotifications(req.query.limit, req.query.name);
496 | returnApiResponse(res, recentNotifications);
497 | });
498 |
499 | /**
500 | * Sends the API response.
501 | * @param {*} res - Response Object.
502 | * @param {*} returnJsonValue Value to be returned from the API
503 | */
504 | function returnApiResponse(res, returnJsonValue) {
505 | res.format({
506 | json: function () {
507 | res.send(returnJsonValue);
508 | },
509 | html: function () {
510 | res.json({
511 | error: "Page not found !!"
512 | });
513 | },
514 | });
515 | }
516 |
517 | // Endpoint to get all the charts data
518 | app.get("/charts", csrfProtection, async function (req, res) {
519 | var returnValue;
520 | try{
521 | if(req.query.name === "all"){
522 | returnValue = await getAllEventsChart();
523 | }
524 | else
525 | returnValue = await setGraphCriteria(req.query.name);
526 |
527 | }catch(err) {
528 | console.log("Error in GET /charts ", err)
529 | }
530 | returnApiResponse(res, returnValue);
531 | });
532 |
533 | /**
534 | * Makes API call and gets the available eventtypes
535 | * @param {Function} callback(eventTypes): called after the getting eventTypes
536 | */
537 | function getEvents(callback) {
538 | request(eventRequestData, function (error, response, body) {
539 | if(error) {
540 | console.error("Error in request getevents ", error);
541 | var errorMessage = {};
542 | errorMessage.message = "Error in getting available EventTypes from ANET. Please try again";
543 | callback(errorMessage)
544 | }
545 | else {
546 | callback(JSON.parse(body))
547 | }
548 | });
549 | }
550 |
551 |
552 | // Handles "/eventtypes" GET endpoint. Calls getEvents() to get available eventTypes.
553 | // Sends the response with error message or eventTypes
554 | app.get('/eventtypes', csrfProtection, function (req, res) {
555 | getEvents(function(allEventTypes) {
556 | res.send(allEventTypes);
557 | });
558 | });
559 |
560 | // error handler
561 | app.use(function (err, req, res, next) {
562 | if (err.code !== 'EBADCSRFTOKEN') return next(err)
563 |
564 | // handle CSRF token errors here
565 | res.status(403);
566 | res.send("CSRF token validation failed. Suspicious request!!");
567 | })
568 |
569 | // Handles invalid URL
570 | app.use((req, res) => {
571 | res.status(404).send("Page Not Found!
");
572 | });
573 |
574 | // App listens on the configured port in config.js file
575 | server.listen(config.app.port, function () {
576 | console.log("Application listening on port ", server.address().port);
577 | });
578 |
--------------------------------------------------------------------------------
/public/javascripts/lib/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v4.1.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,c){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)P(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right',trigger:"hover focus",title:"",delay:0,html:!(_e={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"}),selector:!(de={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)"}),placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},pe="out",ve={HIDE:"hide"+he,HIDDEN:"hidden"+he,SHOW:(me="show")+he,SHOWN:"shown"+he,INSERTED:"inserted"+he,CLICK:"click"+he,FOCUSIN:"focusin"+he,FOCUSOUT:"focusout"+he,MOUSEENTER:"mouseenter"+he,MOUSELEAVE:"mouseleave"+he},Ee="fade",ye="show",Te=".tooltip-inner",Ce=".arrow",Ie="hover",Ae="focus",De="click",be="manual",Se=function(){function i(t,e){if("undefined"==typeof c)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=oe(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),oe(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(oe(this.getTipElement()).hasClass(ye))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),oe.removeData(this.element,this.constructor.DATA_KEY),oe(this.element).off(this.constructor.EVENT_KEY),oe(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&oe(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===oe(this.element).css("display"))throw new Error("Please use show on visible elements");var t=oe.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){oe(this.element).trigger(t);var n=oe.contains(this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!n)return;var i=this.getTipElement(),r=Cn.getUID(this.constructor.NAME);i.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&oe(i).addClass(Ee);var s="function"==typeof this.config.placement?this.config.placement.call(this,i,this.element):this.config.placement,o=this._getAttachment(s);this.addAttachmentClass(o);var a=!1===this.config.container?document.body:oe(this.config.container);oe(i).data(this.constructor.DATA_KEY,this),oe.contains(this.element.ownerDocument.documentElement,this.tip)||oe(i).appendTo(a),oe(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new c(this.element,i,{placement:o,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:Ce},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),oe(i).addClass(ye),"ontouchstart"in document.documentElement&&oe(document.body).children().on("mouseover",null,oe.noop);var l=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,oe(e.element).trigger(e.constructor.Event.SHOWN),t===pe&&e._leave(null,e)};if(oe(this.tip).hasClass(Ee)){var h=Cn.getTransitionDurationFromElement(this.tip);oe(this.tip).one(Cn.TRANSITION_END,l).emulateTransitionEnd(h)}else l()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=oe.Event(this.constructor.Event.HIDE),r=function(){e._hoverState!==me&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),oe(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(oe(this.element).trigger(i),!i.isDefaultPrevented()){if(oe(n).removeClass(ye),"ontouchstart"in document.documentElement&&oe(document.body).children().off("mouseover",null,oe.noop),this._activeTrigger[De]=!1,this._activeTrigger[Ae]=!1,this._activeTrigger[Ie]=!1,oe(this.tip).hasClass(Ee)){var s=Cn.getTransitionDurationFromElement(n);oe(n).one(Cn.TRANSITION_END,r).emulateTransitionEnd(s)}else r();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){oe(this.getTipElement()).addClass(ue+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||oe(this.config.template)[0],this.tip},t.setContent=function(){var t=oe(this.getTipElement());this.setElementContent(t.find(Te),this.getTitle()),t.removeClass(Ee+" "+ye)},t.setElementContent=function(t,e){var n=this.config.html;"object"==typeof e&&(e.nodeType||e.jquery)?n?oe(e).parent().is(t)||t.empty().append(e):t.text(oe(e).text()):t[n?"html":"text"](e)},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getAttachment=function(t){return _e[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)oe(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==be){var e=t===Ie?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Ie?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;oe(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}oe(i.element).closest(".modal").on("hide.bs.modal",function(){return i.hide()})}),this.config.selector?this.config=h({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||oe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),oe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Ae:Ie]=!0),oe(e.getTipElement()).hasClass(ye)||e._hoverState===me?e._hoverState=me:(clearTimeout(e._timeout),e._hoverState=me,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===me&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||oe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),oe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Ae:Ie]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=pe,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===pe&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){return"number"==typeof(t=h({},this.constructor.Default,oe(this.element).data(),"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),Cn.typeCheckConfig(ae,t,this.constructor.DefaultType),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=oe(this.getTipElement()),e=t.attr("class").match(fe);null!==e&&0'}),He=h({},Nn.DefaultType,{content:"(string|element|function)"}),We="fade",xe=".popover-header",Ue=".popover-body",Ke={HIDE:"hide"+ke,HIDDEN:"hidden"+ke,SHOW:(Me="show")+ke,SHOWN:"shown"+ke,INSERTED:"inserted"+ke,CLICK:"click"+ke,FOCUSIN:"focusin"+ke,FOCUSOUT:"focusout"+ke,MOUSEENTER:"mouseenter"+ke,MOUSELEAVE:"mouseleave"+ke},Fe=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var r=i.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(t){we(this.getTipElement()).addClass(Le+"-"+t)},r.getTipElement=function(){return this.tip=this.tip||we(this.config.template)[0],this.tip},r.setContent=function(){var t=we(this.getTipElement());this.setElementContent(t.find(xe),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(Ue),e),t.removeClass(We+" "+Me)},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var t=we(this.getTipElement()),e=t.attr("class").match(je);null!==e&&0=this._offsets[r]&&("undefined"==typeof this._offsets[r+1]||t li > .active",vn='[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',En=".dropdown-toggle",yn="> .dropdown-menu .active",Tn=function(){function i(t){this._element=t}var t=i.prototype;return t.show=function(){var n=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&rn(this._element).hasClass(cn)||rn(this._element).hasClass(un))){var t,i,e=rn(this._element).closest(gn)[0],r=Cn.getSelectorFromElement(this._element);if(e){var s="UL"===e.nodeName?pn:mn;i=(i=rn.makeArray(rn(e).find(s)))[i.length-1]}var o=rn.Event(ln.HIDE,{relatedTarget:this._element}),a=rn.Event(ln.SHOW,{relatedTarget:i});if(i&&rn(i).trigger(o),rn(this._element).trigger(a),!a.isDefaultPrevented()&&!o.isDefaultPrevented()){r&&(t=rn(r)[0]),this._activate(this._element,e);var l=function(){var t=rn.Event(ln.HIDDEN,{relatedTarget:n._element}),e=rn.Event(ln.SHOWN,{relatedTarget:i});rn(i).trigger(t),rn(n._element).trigger(e)};t?this._activate(t,t.parentNode,l):l()}}},t.dispose=function(){rn.removeData(this._element,sn),this._element=null},t._activate=function(t,e,n){var i=this,r=("UL"===e.nodeName?rn(e).find(pn):rn(e).children(mn))[0],s=n&&r&&rn(r).hasClass(fn),o=function(){return i._transitionComplete(t,r,n)};if(r&&s){var a=Cn.getTransitionDurationFromElement(r);rn(r).one(Cn.TRANSITION_END,o).emulateTransitionEnd(a)}else o()},t._transitionComplete=function(t,e,n){if(e){rn(e).removeClass(dn+" "+cn);var i=rn(e.parentNode).find(yn)[0];i&&rn(i).removeClass(cn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}if(rn(t).addClass(cn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),Cn.reflow(t),rn(t).addClass(dn),t.parentNode&&rn(t.parentNode).hasClass(hn)){var r=rn(t).closest(_n)[0];r&&rn(r).find(En).addClass(cn),t.setAttribute("aria-expanded",!0)}n&&n()},i._jQueryInterface=function(n){return this.each(function(){var t=rn(this),e=t.data(sn);if(e||(e=new i(this),t.data(sn,e)),"string"==typeof n){if("undefined"==typeof e[n])throw new TypeError('No method named "'+n+'"');e[n]()}})},o(i,null,[{key:"VERSION",get:function(){return"4.1.1"}}]),i}(),rn(document).on(ln.CLICK_DATA_API,vn,function(t){t.preventDefault(),Tn._jQueryInterface.call(rn(this),"show")}),rn.fn.tab=Tn._jQueryInterface,rn.fn.tab.Constructor=Tn,rn.fn.tab.noConflict=function(){return rn.fn.tab=an,Tn._jQueryInterface},Tn);!function(t){if("undefined"==typeof t)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1===e[0]&&9===e[1]&&e[2]<1||4<=e[0])throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=Cn,t.Alert=In,t.Button=An,t.Carousel=Dn,t.Collapse=bn,t.Dropdown=Sn,t.Modal=wn,t.Popover=On,t.Scrollspy=kn,t.Tab=Pn,t.Tooltip=Nn,Object.defineProperty(t,"__esModule",{value:!0})});
7 | //# sourceMappingURL=bootstrap.min.js.map
--------------------------------------------------------------------------------
/public/javascripts/lib/jquery-3.3.1.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/