commit 1473e39784d395eaffa728df6a832fe753e8a7ca Author: Faisal Zia Date: Mon Nov 5 15:38:59 2018 +0500 OMG-4586 - Initial production dashboard push diff --git a/.dancer b/.dancer new file mode 100644 index 0000000..e69de29 diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..4838444 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,24 @@ +MANIFEST +MANIFEST.SKIP +.dancer +Makefile.PL +cpanfile +config.yml +bin/app.psgi +environments/production.yml +environments/development.yml +lib/ProdDashboard.pm +public/favicon.ico +public/404.html +public/500.html +public/dispatch.fcgi +public/dispatch.cgi +public/css/error.css +public/css/style.css +public/images/perldancer-bg.jpg +public/images/perldancer.jpg +public/javascripts/jquery.js +t/002_index_route.t +t/001_base.t +views/index.tt +views/layouts/main.tt diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP new file mode 100644 index 0000000..8b1c259 --- /dev/null +++ b/MANIFEST.SKIP @@ -0,0 +1,17 @@ +^\.git\/ +maint +^tags$ +.last_cover_stats +Makefile$ +^blib +^pm_to_blib +^.*.bak +^.*.old +^t.*sessions +^cover_db +^.*\.log +^.*\.swp$ +MYMETA.* +^.gitignore +^.svn\/ +^ProdDashboard- diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..ba01925 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,26 @@ +use strict; +use warnings; +use ExtUtils::MakeMaker; + +# Normalize version strings like 6.30_02 to 6.3002, +# so that we can do numerical comparisons on it. +my $eumm_version = $ExtUtils::MakeMaker::VERSION; +$eumm_version =~ s/_//; + +WriteMakefile( + NAME => 'ProdDashboard', + AUTHOR => q{YOUR NAME }, + VERSION_FROM => 'lib/ProdDashboard.pm', + ABSTRACT => 'YOUR APPLICATION ABSTRACT', + ($eumm_version >= 6.3001 + ? ('LICENSE'=> 'perl') + : ()), + PL_FILES => {}, + PREREQ_PM => { + 'Test::More' => 0, + 'YAML' => 0, + 'Dancer2' => 0.206000, + }, + dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, + clean => { FILES => 'ProdDashboard-*' }, +); diff --git a/bin/app.psgi b/bin/app.psgi new file mode 100644 index 0000000..3986b46 --- /dev/null +++ b/bin/app.psgi @@ -0,0 +1,10 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use ProdDashboard; +ProdDashboard->to_app; + diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..372f79d --- /dev/null +++ b/config.yml @@ -0,0 +1,69 @@ +# This is the main configuration file of your Dancer2 app +# env-related settings should go to environments/$env.yml +# all the settings in this file will be loaded at Dancer's startup. + +# Your application's name +appname: "ProdDashboard" + +# The default layout to use for your application (located in +# views/layouts/main.tt) +layout: "main" + +# when the charset is set to UTF-8 Dancer2 will handle for you +# all the magic of encoding and decoding. You should not care +# about unicode within your app when this setting is set (recommended). +charset: "UTF-8" + +# template engine +# simple: default and very basic template engine +# template_toolkit: TT + +template: "template_toolkit" + +# YAML stores on disk +session: YAML + +plugins: + 'Ajax': + content_type: 'application/json' + +engines: + template: + template_toolkit: + start_tag: '<%' + end_tag: '%>' + encoding: 'utf8' + session: + YAML: + session_dir: /tmp/dancer-sessions + +# template: "template_toolkit" +# engines: +# template: +# template_toolkit: +# start_tag: '<%' +# end_tag: '%>' + +# session engine +# +# Simple: in-memory session store - Dancer2::Session::Simple +# YAML: session stored in YAML files - Dancer2::Session::YAML +# +# Check out metacpan for other session storage options: +# https://metacpan.org/search?q=Dancer2%3A%3ASession&search_type=modules +# +# Default value for 'cookie_name' is 'dancer.session'. If you run multiple +# Dancer apps on the same host then you will need to make sure 'cookie_name' +# is different for each app. +# +#engines: +# session: +# Simple: +# cookie_name: testapp.session +# +#engines: +# session: +# YAML: +# cookie_name: eshop.session +# is_secure: 1 +# is_http_only: 1 diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..e23fd8f --- /dev/null +++ b/cpanfile @@ -0,0 +1,11 @@ +requires "Dancer2" => "0.206000"; + +recommends "YAML" => "0"; +recommends "URL::Encode::XS" => "0"; +recommends "CGI::Deurl::XS" => "0"; +recommends "HTTP::Parser::XS" => "0"; + +on "test" => sub { + requires "Test::More" => "0"; + requires "HTTP::Request::Common" => "0"; +}; diff --git a/dbscripts/auto/backup.sh b/dbscripts/auto/backup.sh new file mode 100644 index 0000000..884a6d0 --- /dev/null +++ b/dbscripts/auto/backup.sh @@ -0,0 +1 @@ +PGPASSWORD=oliver99 pg_dump proddashboard -Fc -U postgres -h localhost -f prod_dashboard_temp.sql diff --git a/dbscripts/auto/db_deploy.pl b/dbscripts/auto/db_deploy.pl new file mode 100644 index 0000000..846302d --- /dev/null +++ b/dbscripts/auto/db_deploy.pl @@ -0,0 +1,329 @@ +#!/usr/bin/perl + +############################################################################### +# +# DB deployment automation +# must always be run from the target auto folder. +# it will expect the sprint folders to be at ../s1 etc +# +# always backup DB before a deploy +# pg_dump OMG -Fc -U postgres -h localhost > omg_backup.sql +############################################################################### + +use DBI; +use Data::Dumper; +use strict; + +my $C_PGEXEC = 'PGPASSWORD=oliver99 psql -U postgres -d proddashboard -h localhost -1 -f '; +my $C_LOG = "pg_log.txt"; +my $C_TEAM1_LOW = 1; +my $C_TEAM1_HIGH = 99; +my $C_TEAM2_LOW = 100; +my $C_TEAM2_HIGH = 199; +my $C_SPRINT_DONE = 1; +my $C_TEAM_DONE = 2; + + +############################################################################### +# writeLog +############################################################################### + +sub writeLog { + my ($status, $cmd, $output) = @_; + + open(LOGFILE,">>$C_LOG") or die("Can't open log file.\n"); + print LOGFILE ("$status RUNNING $cmd\n"); + print LOGFILE ("$output\n"); + print LOGFILE ("--------------------------------------------------------------------------------------\n"); + close(LOGFILE); +} + + +############################################################################### +# checkInit +# check if deploy init had been done +############################################################################### +sub checkInit{ + my ($dbh) = @_; + my $drecs = 0; + + #check whether deploy table exists + my $sql = qq{SELECT count(*) FROM information_schema.tables where table_name = 'deploy' and table_schema = 'public'}; + my $sth = $dbh->prepare($sql); + $sth->execute(); + + ($drecs) = $sth->fetchrow_array; + + print "Found $drecs instances of deploy\n"; + return $drecs; + +} + +############################################################################### +# updateDeploy +# add completed deployment item to tracking db +############################################################################### +sub updateDeploy{ + my ($dbh, $sprint, $item, $file) = @_; + + my $cmd = $C_PGEXEC.$file; + + my $log = `$cmd`; + if ( $? == -1 ) + { + writeLog("FAILED", $cmd, $log); + print "command $cmd failed: $!\n"; + exit(0); + } + else{ + writeLog("OK", $cmd, $log); + print $log.'\n'; + + eval{ + + my $sql = qq{insert into public.deploy (sprint_id, script_id, status) values (?, ?, ?)}; + my $sth = $dbh->prepare($sql); + $sth->execute( $sprint, $item, 't' ); + }; + + if ($@){ + print "Error updating Deploy entry for sprint $sprint, item $item \n"; + exit(0); + } + + } + + +} + +############################################################################### +# checkFailuresExist +# check if failed item found +############################################################################### +sub checkFailuresExist{ + my ($dbh) = @_; + + my $failed; + + # check last item of last sprint + my $sql = qq{SELECT count(*) FROM public.deploy where status != TRUE}; + my $sth = $dbh->prepare($sql); + + $sth->execute(); + + ($failed) = $sth->fetchrow_array; + + return $failed; +} + +############################################################################### +# checkLastDeploy +# check the last sprint/id deployed for this team range +############################################################################### +sub checkLastDeploy{ + my ($dbh, $high, $low) = @_; + + my %state; + my @row; + + # check last item of last sprint + my $sql = qq{SELECT sprint_id as sprintid, script_id as scriptid FROM public.deploy WHERE script_id <= ? and script_id >= ? order by sprint_id DESC, script_id DESC limit 1}; + my $sth = $dbh->prepare($sql); + $sth->execute( $high, $low ); + + @row = $sth->fetchrow_array; + if (@row){ + print "values found\n"; + $state{sprint} = $row[0]; + $state{item} = $row[1]; + } + else{ + print "values NOT found\n"; + $state{sprint} = 1; + $state{item} = $low - 1; + } + #print Dumper( \@row ); + + return \%state; +} + +############################################################################### +# deploy +# deploy all incomplete items from specified item + 1 +# it will only deploy items in this team range +############################################################################### +sub deploy{ + my ($dbh, $sprint, $item, $low) = @_; + print "Beginning deployment at sprint $sprint item $item \n"; + + my $cont = 1; + do { + + # check for next sprint item to deploy + $item++; + + my $spath = "../s".$sprint."/deploy/".$item."_*.sql"; + print "checking $spath \n"; + + my @files = glob($spath); + my $numfiles = @files; + + if ( $numfiles ){ + print "found deploy file for sprint $sprint item $item as $files[0] \n"; + updateDeploy( $dbh, $sprint, $item, $files[0] ); + } + else{ + print "no more items in current sprint\n"; + + # check next sprint + $sprint++; + $item = $low-1; + $spath = "../s".$sprint."/deploy/"; + + if (-e $spath){ + print "checking next sprint $sprint\n"; + } + else{ + print "Deploy complete\n"; + $cont = 0; + } + } + + } while ($cont); +} + +############################################################################### +# deploy_next +# deploy all incomplete items from specified item + 1 within this +# sprint only. +# it will only deploy items in this team range +############################################################################### +sub deploy_next{ + my ($dbh, $sprint, $item, $low) = @_; + print "Beginning deployment at sprint $sprint item $item low $low \n"; + + my $cont = 1; + do { + + # check for next sprint item to deploy + $item++; + + my $spath = "../s".$sprint."/deploy/".$item."_*.sql"; + print "checking $spath \n"; + + my @files = glob($spath); + my $numfiles = @files; + + if ( $numfiles ){ + print "found deploy file for sprint $sprint item $item as $files[0] \n"; + updateDeploy( $dbh, $sprint, $item, $files[0] ); + } + else{ + print "no more items in current sprint\n"; + + # check next sprint + $sprint++; + $item = $low-1; + $spath = "../s".$sprint."/deploy/"; + + if (-e $spath){ + print "next sprint available\n"; + return $C_SPRINT_DONE; + } + else{ + print "Deploy complete\n"; + return $C_TEAM_DONE; + } + } + + } while ($cont); +} + +sub main{ + print "Begin Deploy\n"; + my $sync = 0; + + my $dbh = DBI->connect('dbi:Pg:dbname=proddashboard;host=localhost','pgdev','pgdev',{AutoCommit=>1,RaiseError=>1,PrintError=>0}); + + # check if init done + my $init = checkInit( $dbh ); + + if ($init){ + print "init done\n"; + } + else{ + print "init has not been run - please run init_deploy.sh from the command line\n"; + exit(0); + } + + # now get the last deployment entry on this system + #my ($sprint, $item) = checkLastDeploy( $dbh ); + if ( checkFailuresExist( $dbh ) ){ + print "Failures exist - please resolve first\n"; + exit(0); + } + + print "no failures found - checking last deploy point for TEAM1\n"; + + # get current deploy state for both teams + my ($team1state, $team2state, $first, $second, $cont, $sprint, $res1, $res2); + $team1state = checkLastDeploy( $dbh, $C_TEAM1_HIGH, $C_TEAM1_LOW ); + $team2state = checkLastDeploy( $dbh, $C_TEAM2_HIGH, $C_TEAM2_LOW ); + $team2state->{low} = $C_TEAM2_LOW; + $team2state->{high} = $C_TEAM2_HIGH; + $team1state->{low} = $C_TEAM1_LOW; + $team1state->{high} = $C_TEAM1_HIGH; + + # choose lowest sprint + if ($team2state->{sprint} < $team1state->{sprint}){ + $first = $team2state; + $second = $team1state; + } + else{ + $first = $team1state; + $second = $team2state; + } + + # now start on first team and sync sprint by sprint + $cont = 1; + $sprint = $first->{sprint}; + do{ + # check if sprints are in sync and make sure team1 is first + if ($second->{sprint} == $first->{sprint}){ + $first = $team1state; + $second = $team2state; + $sync = 1; + } + + # run + $res1 = deploy_next( $dbh, $first->{sprint}, $first->{item}, $first->{low} ); + # move to next sprint + $first->{sprint} = $first->{sprint} + 1; + $first->{item} = $first->{low} - 1; + + + # check if second team sprint caught up + $res2 = 0; + if ($sync){ + # run this sprint + $res2 = deploy_next( $dbh, $second->{sprint}, $second->{item}, $second->{low} ); + $second->{sprint} = $second->{sprint} + 1; + $second->{item} = $second->{low} - 1; + } + + # now check if this completed the deploy or more sprints left + if ($res1 == $C_TEAM_DONE){ + $cont = 0; + } + } while ($cont); + + print "End Deploy\n"; +} + + +# +# run main +# +main(); + +# end diff --git a/dbscripts/auto/deploy_table_create.sql b/dbscripts/auto/deploy_table_create.sql new file mode 100644 index 0000000..8a8fa51 --- /dev/null +++ b/dbscripts/auto/deploy_table_create.sql @@ -0,0 +1,23 @@ + +/************************************************************ +* Author : Geetha Nair dashboard_backup.sql +############################################################################### + +use DBI; +use Data::Dumper; +use strict; + +my $C_PGEXEC = 'PGPASSWORD=oliver99 psql -U postgres -d proddashboard -h localhost -1 -f '; +my $C_LOG = "pg_log.txt"; + +############################################################################### +# writeLog +############################################################################### + +sub writeLog { + my ($status, $cmd, $output) = @_; + + open(LOGFILE,">>$C_LOG") or die("Can't open log file.\n"); + print LOGFILE ("$status RUNNING $cmd\n"); + print LOGFILE ("$output\n"); + print LOGFILE ("--------------------------------------------------------------------------------------\n"); + close(LOGFILE); +} + + +############################################################################### +# updateDeploy +# add completed deployment item to tracking db +############################################################################### +sub updateDeploy{ + my ($dbh, $file) = @_; + + my $cmd = $C_PGEXEC.$file; + + my $log = `$cmd`; + if ( $? == -1 ) + { + writeLog("FAILED", $cmd, $log); + print "command $cmd failed: $!\n"; + exit(0); + } + else{ + writeLog("OK", $cmd, $log); + print $log.'\n'; + } +} + + +############################################################################### +# deploy_next +# deploy all incomplete items from specified item + 1 within this +# sprint only. +############################################################################### +sub deploy_next{ + my ($dbh, $item) = @_; + print "Beginning deployment at item $item \n"; + + my $spath = "../supportscripts/".$item."_*.sql"; + print "checking $spath \n"; + + my @files = glob($spath); + my $numfiles = @files; + + if ( $numfiles ){ + print "found deploy file for item $item as $files[0] \n"; + updateDeploy( $dbh, $files[0] ); + } + else{ + print "no more items\n"; + return -1; + } + +} + +sub main{ + print "Begin Support Deploy\n"; + + my $dbh = DBI->connect('dbi:Pg:dbname=proddashboard;host=localhost','pgdev','pgdev',{AutoCommit=>1,RaiseError=>1,PrintError=>0}); + + my $cont = 1; + my $item = 1; + my $res1 = 0; + do{ + # run + $res1 = deploy_next( $dbh, $item ); + + # move to next item + $item++; + + # now check if this completed the deploy or more sprints left + if ($res1 == -1){ + $cont = 0; + } + } while ($cont); + + print "End Support Deploy\n"; +} + + +# +# run main +# +main(); + +# end \ No newline at end of file diff --git a/dbscripts/s1/deploy/100_db_4586_prod_servers_create.sql b/dbscripts/s1/deploy/100_db_4586_prod_servers_create.sql new file mode 100644 index 0000000..cad4312 --- /dev/null +++ b/dbscripts/s1/deploy/100_db_4586_prod_servers_create.sql @@ -0,0 +1,24 @@ +/******************************************************************************************** +* Author : Faisal Zia +* Date : 05/11/2018 +* Description : create prod_logs table +* Jira Ticket : https://oliveruk.atlassian.net/browse/OMG-4586 +***********************************************************************************************************************************/ + + +-- Table: prod_logs + +DROP TABLE IF EXISTS prod_logs; + +CREATE TABLE prod_logs +( + id serial, + log_time timestamp with time zone, + activity character varying(80), + prod_server_id character varying, + message text, + CONSTRAINT prod_logs_pkey PRIMARY KEY (id) +) +WITH ( + OIDS=FALSE +); diff --git a/dbscripts/s1/deploy/104_db_4586_create_prod_log_entry.sql b/dbscripts/s1/deploy/104_db_4586_create_prod_log_entry.sql new file mode 100644 index 0000000..493110c --- /dev/null +++ b/dbscripts/s1/deploy/104_db_4586_create_prod_log_entry.sql @@ -0,0 +1,30 @@ +/******************************************************************************************** +* Author : Faisal Zia +* Date : 05/11/2018 +* Description : Create a log entry in otp log table +* Jira Ticket : https://oliveruk.atlassian.net/browse/OMG-4586 +*********************************************************************************************/ + +-- Function: create_prod_log_entry(character varying, character varying, text) + +DROP FUNCTION IF EXISTS create_prod_log_entry(character varying, character varying, text); + +CREATE OR REPLACE FUNCTION create_prod_log_entry( + prodserver_id character varying, + log_activity character varying, + log_msg text) + RETURNS integer AS +$BODY$ + DECLARE + lastid int; +BEGIN + -- insert a new record into prod_logs + INSERT INTO "public"."prod_logs" (prod_server_id, activity, message, log_time) + VALUES (prodserver_id, log_activity, log_msg, now()) RETURNING id INTO lastid; + + RETURN lastid; + +END; +$BODY$ + LANGUAGE plpgsql VOLATILE; + \ No newline at end of file diff --git a/environments/development.yml b/environments/development.yml new file mode 100644 index 0000000..d370f29 --- /dev/null +++ b/environments/development.yml @@ -0,0 +1,44 @@ +# configuration file for development environment + +# the logger engine to use +# console: log messages to STDOUT (your console where you started the +# application server) +# file: log message to a file in log/ +logger: "console" + +# the log level for this environment +# core is the lowest, it shows Dancer2's core log messages as well as yours +# (debug, info, warning and error) +log: "core" + +# should Dancer2 consider warnings as critical errors? +warnings: 1 + +# should Dancer2 show a stacktrace when an 5xx error is caught? +# if set to yes, public/500.html will be ignored and either +# views/500.tt, 'error_template' template, or a default error template will be used. +show_errors: 1 + +# print the banner +startup_info: 1 + +# database +plugins: + Database: + # default DB connection + driver: 'Pg' + database: 'proddashboard' + host: '127.0.0.1' + port: 5432 + username: 'pgdev' + password: 'pgdev' + connection_check_threshold: 20 + dbi_params: + RaiseError: 1 + AutoCommit: 1 + log_queries: 1 + +domain: 'http://192.168.154.138:5000/' + +admin_email: "devops@oliver.agency" +sender_email: "devops@oliver.agency" \ No newline at end of file diff --git a/environments/production.yml b/environments/production.yml new file mode 100644 index 0000000..41b436f --- /dev/null +++ b/environments/production.yml @@ -0,0 +1,16 @@ +# configuration file for production environment + +# only log warning and error messsages +log: "warning" + +# log message to a file in logs/ +logger: "file" + +# don't consider warnings critical +warnings: 0 + +# hide errors +show_errors: 0 + +# disable server tokens in production environments +no_server_tokens: 1 diff --git a/lib/ProdDashboard.pm b/lib/ProdDashboard.pm new file mode 100644 index 0000000..740ab04 --- /dev/null +++ b/lib/ProdDashboard.pm @@ -0,0 +1,19 @@ +package ProdDashboard; +use Dancer2; +use Dancer2::Plugin::Ajax; +use homepage::homepage_controller; +use production::production_controller; + +our $VERSION = '0.1'; + +prefix "/"; + +################################################################# +# Route Home page +################################################################# +get '/' => \&homepage_controller::showHomepage; + +########## API ROUTES ########################################### +post '/api/registerServer' => \&production_controller::registerServers; + +true; diff --git a/lib/email/.DS_Store b/lib/email/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/lib/email/.DS_Store differ diff --git a/lib/email/email_helper.pm b/lib/email/email_helper.pm new file mode 100644 index 0000000..e40ac9e --- /dev/null +++ b/lib/email/email_helper.pm @@ -0,0 +1,36 @@ +package email_helper; + +use Dancer2 appname => 'ProdDashboard'; +use utils::Mailer; +use Data::Dumper; +use production::production_logs_helper; + +# sends email +sub sendMail{ + my ($fromAddress, $toemailAddresses, $emailSubject, $emailBody) = @_; + + if( $fromAddress eq "" ){ + production_logs_helper::addLogEntry(0, 'MAIL ISSUE', 'Issue in sending status email. From address missing.'); + return 0; + } + + if( $toemailAddresses eq "" ){ + production_logs_helper::addLogEntry(0, 'MAIL ISSUE', 'Issue in sending status email. To address missing.'); + return 0; + } + + my $mailSuccessflag = Mailer::send_mail( + $fromAddress, + $toemailAddresses, + $emailBody, + $emailSubject + ); + + if(! $mailSuccessflag ){ + production_logs_helper::addLogEntry(0, 'MAIL ISSUE', 'Issue in sending status email.'); + return 0; + }else{ + return 1; + } +} +1; diff --git a/lib/errors/AccessDenied.pm b/lib/errors/AccessDenied.pm new file mode 100644 index 0000000..4b808ec --- /dev/null +++ b/lib/errors/AccessDenied.pm @@ -0,0 +1,7 @@ +package errors::AccessDenied; + +use Moo; + +extends 'errors::Error'; + +1; diff --git a/lib/errors/ActivityDenied.pm b/lib/errors/ActivityDenied.pm new file mode 100644 index 0000000..9665d22 --- /dev/null +++ b/lib/errors/ActivityDenied.pm @@ -0,0 +1,7 @@ +package errors::ActivityDenied; + +use Moo; + +extends 'errors::Error'; + +1; diff --git a/lib/errors/ArgumentError.pm b/lib/errors/ArgumentError.pm new file mode 100644 index 0000000..d54ae03 --- /dev/null +++ b/lib/errors/ArgumentError.pm @@ -0,0 +1,7 @@ +package errors::ArgumentError; + +use Moo; + +extends 'errors::Error'; + +1; diff --git a/lib/errors/Error.pm b/lib/errors/Error.pm new file mode 100644 index 0000000..432c67a --- /dev/null +++ b/lib/errors/Error.pm @@ -0,0 +1,24 @@ +package errors::Error; + +use strict; +use warnings; + +use Moo; + +with 'Throwable'; + +has 'message' => ( + is => 'ro', + required => 1, +); + +has 'target' => ( + is => 'ro' +); + +use overload + '""' => \&to_string; + +sub to_string { $_[0]->message; } + +1; diff --git a/lib/errors/customExceptions.pm b/lib/errors/customExceptions.pm new file mode 100644 index 0000000..dc1aceb --- /dev/null +++ b/lib/errors/customExceptions.pm @@ -0,0 +1,12 @@ +package errors::customExceptions; + +use errors::AccessDenied; +use errors::ActivityDenied; +use errors::ArgumentError; +use errors::dbError; +use errors::invalidInput; +use errors::invalidObject; +use errors::lostResource; +use errors::notFound; + +1; \ No newline at end of file diff --git a/lib/errors/dbError.pm b/lib/errors/dbError.pm new file mode 100644 index 0000000..a628cb4 --- /dev/null +++ b/lib/errors/dbError.pm @@ -0,0 +1,249 @@ +package errors::dbError; + +use Dancer2 appname => 'ProdDashboard'; + +use Moo; +extends 'errors::Error'; + +my %ERROR_STATES = ( + "03000" => "SqlStatementNotYetComplete", + "08000" => "ConnectionException", + "08003" => "ConnectionDoesNotExist", + "08006" => "ConnectionFailure", + "08001" => "SqlclientUnableToEstablishSqlconnection", + "08004" => "SqlserverRejectedEstablishmentOfSqlconnection", + "08007" => "TransactionResolutionUnknown", + "08P01" => "ProtocolViolation", + "09000" => "TriggeredActionException", + "0A000" => "FeatureNotSupported", + "0B000" => "InvalidTransactionInitiation", + "0F000" => "LocatorException", + "0F001" => "InvalidLocatorSpecification", + "0L000" => "InvalidGrantor", + "0LP01" => "InvalidGrantOperation", + "0P000" => "InvalidRoleSpecification", + "20000" => "CaseNotFound", + "21000" => "CardinalityViolation", + "22000" => "DataException", + "2202E" => "ArraySubscriptError", + "22021" => "CharacterNotInRepertoire", + "22008" => "DatetimeFieldOverflow", + "22012" => "DivisionByZero", + "22005" => "ErrorInAssignment", + "2200B" => "EscapeCharacterConflict", + "22022" => "IndicatorOverflow", + "22015" => "IntervalFieldOverflow", + "2201E" => "InvalidArgumentForLogarithm", + "22014" => "InvalidArgumentForNtileFunction", + "22016" => "InvalidArgumentForNthValueFunction", + "2201F" => "InvalidArgumentForPowerFunction", + "2201G" => "InvalidArgumentForWidthBucketFunction", + "22018" => "InvalidCharacterValueForCast", + "22007" => "InvalidDatetimeFormat", + "22019" => "InvalidEscapeCharacter", + "2200D" => "InvalidEscapeOctet", + "22025" => "InvalidEscapeSequence", + "22P06" => "NonstandardUseOfEscapeCharacter", + "22010" => "InvalidIndicatorParameterValue", + "22023" => "InvalidParameterValue", + "2201B" => "InvalidRegularExpression", + "2201W" => "InvalidRowCountInLimitClause", + "2201X" => "InvalidRowCountInResultOffsetClause", + "22009" => "InvalidTimeZoneDisplacementValue", + "2200C" => "InvalidUseOfEscapeCharacter", + "2200G" => "MostSpecificTypeMismatch", + "22004" => "NullValueNotAllowed", + "22002" => "NullValueNoIndicatorParameter", + "22003" => "NumericValueOutOfRange", + "22026" => "StringDataLengthMismatch", + "22001" => "StringDataRightTruncation", + "22011" => "SubstringError", + "22027" => "TrimError", + "22024" => "UnterminatedCString", + "2200F" => "ZeroLengthCharacterString", + "22P01" => "FloatingPointException", + "22P02" => "InvalidTextRepresentation", + "22P03" => "InvalidBinaryRepresentation", + "22P04" => "BadCopyFileFormat", + "22P05" => "UntranslatableCharacter", + "2200L" => "NotAnXmlDocument", + "2200M" => "InvalidXmlDocument", + "2200N" => "InvalidXmlContent", + "2200S" => "InvalidXmlComment", + "2200T" => "InvalidXmlProcessingInstruction", + "23000" => "IntegrityConstraintViolation", + "23001" => "RestrictViolation", + "23502" => "NotNullViolation", + "23503" => "ForeignKeyViolation", + "23505" => "UniqueViolation", + "23514" => "CheckViolation", + "23P01" => "ExclusionViolation", + "24000" => "InvalidCursorState", + "25000" => "InvalidTransactionState", + "25001" => "ActiveSqlTransaction", + "25002" => "BranchTransactionAlreadyActive", + "25008" => "HeldCursorRequiresSameIsolationLevel", + "25003" => "InappropriateAccessModeForBranchTransaction", + "25004" => "InappropriateIsolationLevelForBranchTransaction", + "25005" => "NoActiveSqlTransactionForBranchTransaction", + "25006" => "ReadOnlySqlTransaction", + "25007" => "SchemaAndDataStatementMixingNotSupported", + "25P01" => "NoActiveSqlTransaction", + "25P02" => "InFailedSqlTransaction", + "26000" => "InvalidSqlStatementName", + "27000" => "TriggeredDataChangeViolation", + "28000" => "InvalidAuthorizationSpecification", + "28P01" => "InvalidPassword", + "2B000" => "DependentPrivilegeDescriptorsStillExist", + "2BP01" => "DependentObjectsStillExist", + "2D000" => "InvalidTransactionTermination", + "2F000" => "SqlRoutineException", + "34000" => "InvalidCursorName", + "38000" => "ExternalRoutineException", + "39000" => "ExternalRoutineInvocationException", + "3B000" => "SavepointException", + "3B001" => "InvalidSavepointSpecification", + "3D000" => "InvalidCatalogName", + "3F000" => "InvalidSchemaName", + "40000" => "TransactionRollback", + "40002" => "TransactionIntegrityConstraintViolation", + "40001" => "SerializationFailure", + "40003" => "StatementCompletionUnknown", + "40P01" => "DeadlockDetected", + "42000" => "SyntaxErrorOrAccessRuleViolation", + "42601" => "SyntaxError", + "42501" => "InsufficientPrivilege", + "42846" => "CannotCoerce", + "42803" => "GroupingError", + "42P20" => "WindowingError", + "42P19" => "InvalidRecursion", + "42830" => "InvalidForeignKey", + "42602" => "InvalidName", + "42622" => "NameTooLong", + "42939" => "ReservedName", + "42804" => "DatatypeMismatch", + "42P18" => "IndeterminateDatatype", + "42P21" => "CollationMismatch", + "42P22" => "IndeterminateCollation", + "42809" => "WrongObjectType", + "42703" => "UndefinedColumn", + "42883" => "UndefinedFunction", + "42P01" => "UndefinedTable", + "42P02" => "UndefinedParameter", + "42704" => "UndefinedObject", + "42701" => "DuplicateColumn", + "42P03" => "DuplicateCursor", + "42P04" => "DuplicateDatabase", + "42723" => "DuplicateFunction", + "42P05" => "DuplicatePreparedStatement", + "42P06" => "DuplicateSchema", + "42P07" => "DuplicateTable", + "42712" => "DuplicateAlias", + "42710" => "DuplicateObject", + "42702" => "AmbiguousColumn", + "42725" => "AmbiguousFunction", + "42P08" => "AmbiguousParameter", + "42P09" => "AmbiguousAlias", + "42P10" => "InvalidColumnReference", + "42611" => "InvalidColumnDefinition", + "42P11" => "InvalidCursorDefinition", + "42P12" => "InvalidDatabaseDefinition", + "42P13" => "InvalidFunctionDefinition", + "42P14" => "InvalidPreparedStatementDefinition", + "42P15" => "InvalidSchemaDefinition", + "42P16" => "InvalidTableDefinition", + "42P17" => "InvalidObjectDefinition", + "44000" => "WithCheckOptionViolation", + "53000" => "InsufficientResources", + "53100" => "DiskFull", + "53200" => "OutOfMemory", + "53300" => "TooManyConnections", + "54000" => "ProgramLimitExceeded", + "54001" => "StatementTooComplex", + "54011" => "TooManyColumns", + "54023" => "TooManyArguments", + "55000" => "ObjectNotInPrerequisiteState", + "55006" => "ObjectInUse", + "55P02" => "CantChangeRuntimeParam", + "55P03" => "LockNotAvailable", + "57000" => "OperatorIntervention", + "57014" => "QueryCanceled", + "57P01" => "AdminShutdown", + "57P02" => "CrashShutdown", + "57P03" => "CannotConnectNow", + "57P04" => "DatabaseDropped", + "58000" => "SystemError", # added manually + "58030" => "IoError", + "58P01" => "UndefinedFile", + "58P02" => "DuplicateFile", + "F0000" => "ConfigFileError", + "F0001" => "LockFileExists", + "HV000" => "FdwError", + "HV005" => "FdwColumnNameNotFound", + "HV002" => "FdwDynamicParameterValueNeeded", + "HV010" => "FdwFunctionSequenceError", + "HV021" => "FdwInconsistentDescriptorInformation", + "HV024" => "FdwInvalidAttributeValue", + "HV007" => "FdwInvalidColumnName", + "HV008" => "FdwInvalidColumnNumber", + "HV004" => "FdwInvalidDataType", + "HV006" => "FdwInvalidDataTypeDescriptors", + "HV091" => "FdwInvalidDescriptorFieldIdentifier", + "HV00B" => "FdwInvalidHandle", + "HV00C" => "FdwInvalidOptionIndex", + "HV00D" => "FdwInvalidOptionName", + "HV090" => "FdwInvalidStringLengthOrBufferLength", + "HV00A" => "FdwInvalidStringFormat", + "HV009" => "FdwInvalidUseOfNullPointer", + "HV014" => "FdwTooManyHandles", + "HV001" => "FdwOutOfMemory", + "HV00P" => "FdwNoSchemas", + "HV00J" => "FdwOptionNameNotFound", + "HV00K" => "FdwReplyHandle", + "HV00Q" => "FdwSchemaNotFound", + "HV00R" => "FdwTableNotFound", + "HV00L" => "FdwUnableToCreateExecution", + "HV00M" => "FdwUnableToCreateReply", + "HV00N" => "FdwUnableToEstablishConnection", + "P0000" => "PlpgsqlError", + "P0001" => "RaiseException", + "P0002" => "NoDataFound", + "P0003" => "TooManyRows", + "S1000" => "GeneralError", # added manually + "XX000" => "InternalError", + "XX001" => "DataCorrupted", + "XX002" => "IndexCorrupted", +); + +has 'state' => ( + is => 'ro', + required => 1, +); + +has 'code' => ( + is => 'ro', + required => 1, +); + +sub type { + my $self = shift; + return $ERROR_STATES{$self->state}; +} + +sub from_handle { + my ($class, $handle, $caller) = @_; + + my $error_state = $handle->state; + my $error_msg = $handle->errstr; + my $error_code = $handle->err; + + error "$caller query failure. Error State: $error_state, Error message : $error_msg"; + + return $class->new ( + message => $error_msg, + state => $error_state, + code => $error_code, + ); +} + +1; diff --git a/lib/errors/invalidInput.pm b/lib/errors/invalidInput.pm new file mode 100644 index 0000000..9935028 --- /dev/null +++ b/lib/errors/invalidInput.pm @@ -0,0 +1,7 @@ +package errors::invalidInput; + +use Moo; + +extends 'errors::Error'; + +1; diff --git a/lib/errors/invalidObject.pm b/lib/errors/invalidObject.pm new file mode 100644 index 0000000..e3c5087 --- /dev/null +++ b/lib/errors/invalidObject.pm @@ -0,0 +1,7 @@ +package errors::invalidObject; + +use Moo; + +extends 'errors::Error'; + +1; diff --git a/lib/errors/lostResource.pm b/lib/errors/lostResource.pm new file mode 100644 index 0000000..78eed1f --- /dev/null +++ b/lib/errors/lostResource.pm @@ -0,0 +1,7 @@ +package errors::lostResource; + +use Moo; + +extends 'errors::Error'; + +1; diff --git a/lib/errors/notFound.pm b/lib/errors/notFound.pm new file mode 100644 index 0000000..de851cc --- /dev/null +++ b/lib/errors/notFound.pm @@ -0,0 +1,7 @@ +package errors::notFound; + +use Moo; + +extends 'errors::Error'; + +1; \ No newline at end of file diff --git a/lib/homepage/homepage_controller.pm b/lib/homepage/homepage_controller.pm new file mode 100644 index 0000000..2f96c36 --- /dev/null +++ b/lib/homepage/homepage_controller.pm @@ -0,0 +1,34 @@ +package homepage_controller; + +use Template; +use Dancer2 appname => 'ProdDashboard'; +use Data::Dumper; +use strict; +use warnings; +use TryCatch; +use production::production_helper; +use DateTime; +use DateTime::Format::Strptime; + +use utils::ajax::response_helper; +use POSIX (); + +sub showHomepage { + my $error; + try { + print "\n\n Before server list print \n\n"; + my $servers_list = production_helper::getAllProdServers(); + print "\n\n Before server list print \n\n"; + print Dumper $servers_list; + my $local_domain = config->{domain}; + + return template 'homepage/homepage', { + servers_list => $servers_list, + domain => $local_domain + }; + } catch ($error){ + die $error; + }; +} + +1; diff --git a/lib/production/dao/production_db.pm b/lib/production/dao/production_db.pm new file mode 100644 index 0000000..61c8fc2 --- /dev/null +++ b/lib/production/dao/production_db.pm @@ -0,0 +1,160 @@ +package production_db; + +use Dancer2 appname => 'ProdDashboard'; +use Dancer2::Plugin::Database; +use TryCatch; +use Data::Dumper; + +use errors::dbError; + +sub registerServer { + my ($server_id, $status) = @_; + + my $sth; + my $ser_id; + + try { + $sth = database->prepare("select * from register_prod_server (?, ?)"); + $sth->execute( $server_id, $status ); + + $ser_id = $sth->fetchrow_array; + + } catch { + errors::dbError->from_handle($sth, 'registerServer')->throw(); + } + + return $ser_id; +} + +sub updateServer { + my ($server_id, $status) = @_; + + my $sth; + my $ser_id; + + try { + $sth = database->prepare("select * from register_prod_server (?, ?)"); + $sth->execute( $server_id, $status ); + + $ser_id = $sth->fetchrow_array; + + } catch { + errors::dbError->from_handle($sth, 'updateServer')->throw(); + } + + return $ser_id; +} + +sub getAllProdServers{ + my $sth; + my $servers_list; + + try { + $sth = database->prepare("select * from get_all_prod_servers ()"); + $sth->execute(); + + $servers_list = $sth->fetchall_arrayref({}); + + } catch { + errors::dbError->from_handle($sth, 'getAllProdServers')->throw(); + } + + return $servers_list; +} + +sub addLogEntry{ + my ($hub_number, $activity, $message, $channel_id, $file_name, $file_size) = @_; + + my $sth; + my $log_record_id; + + try { + $sth = database->prepare("select * from create_otp_log_entry (?, ?, ?, ?, ?, ?)"); + $sth->execute( $hub_number, $activity, $message, $channel_id, $file_name, $file_size ); + + $log_record_id = $sth->fetchrow_array; + + } catch { + errors::dbError->from_handle($sth, 'addLogEntry')->throw(); + } + + return $log_record_id; +} + +# Deletes all channels for single HUB ID passed +sub deleteAllChannels{ + my $hub_id = shift; + + my $sth; + my $node_id; + + try { + $sth = database->prepare("select * from delete_node_channels (?)"); + $sth->execute( $hub_id ); + + } catch { + errors::dbError->from_handle($sth, 'deleteAllChannels')->throw(); + } + + return; +} + +# Adds a channel record in otp_channels table +sub addNodeChannel{ + my ($hub_id, $channel_id, $destination_id, $RAG_status, $channel_name) = @_; + + my $sth; + my $new_channel_id; + + try { + $sth = database->prepare("select * from add_node_channel (?, ?, ?, ?, ?)"); + $sth->execute( $hub_id, $channel_id, $destination_id, $RAG_status, $channel_name ); + + $new_channel_id = $sth->fetchrow_array; + + } catch { + errors::dbError->from_handle($sth, 'addNodeConfig')->throw(); + } + + return $new_channel_id; +} + +# gets list of all channels for passed hub id +sub getAllChannels{ + my $hub_id = shift; + + my $sth; + my $channels_list; + + try { + $sth = database->prepare("select * from get_all_otp_channels ( ? )"); + $sth->execute( $hub_id ); + + $channels_list = $sth->fetchall_arrayref({}); + + } catch { + errors::dbError->from_handle($sth, 'getAllChannels')->throw(); + } + + return $channels_list; +} + +# update file_name field in channels table with passed file_name +# file name is only populated if file is in progress (being transferred) for that channel +# otherwise field is kept empty +sub updateChannelTransferActivity{ + my($hub_id, $channel_id, $file_name) = @_; + my $sth; + + try { + $sth = database->prepare("select * from channel_update_filename (?, ?, ?)"); + $sth->execute( $hub_id, $channel_id, $file_name ); + + } catch { + errors::dbError->from_handle($sth, 'updateChannelTransferActivity')->throw(); + } + + return; +} + +1; \ No newline at end of file diff --git a/lib/production/production_controller.pm b/lib/production/production_controller.pm new file mode 100644 index 0000000..163051e --- /dev/null +++ b/lib/production/production_controller.pm @@ -0,0 +1,154 @@ +package production_controller; + +use Template; +use Dancer2 appname => 'ProdDashboard'; +use Data::Dumper; +use TryCatch; + +use utils::ajax::response_helper; +use utils::support::action_helper; +use production::production_helper; +use production::production_logs_helper; + +my $GREEN_COLOR = 1; +my $AMBER_COLOR = 2; +my $RED_COLOR = 3; + +sub registerServers { + my $error; + + try { + my $post_params = params(); + my $server_id = $post_params->{'server_id'}; + my $status = $post_params->{'status'}; + + my $registration_id = production_helper::registerServer($server_id, $status); + + return ajax_data_response( + message => 'Successful.' + ); + } catch($error){ + return handleException( $error ); + }; +} + +sub updateServers{ + my $error; + + try { + my $server_id = param('server_id'); + my $status = param('status'); + my $message = param('msg'); + + my $db_status = $AMBER_COLOR; # If status not received, set to AMBER initially. + + if(defined $status){ + if($status eq "OK"){ + $db_status = $GREEN_COLOR; + }else{ + $db_status = $RED_COLOR; + production_logs_helper::addLogEntry($server_id, '', $message); + } + } + + my $updated_server = production_helper::updateServer($server_id, $db_status); + + return ajax_data_response( + message => 'Successful.' + ); + + } catch($error){ + return handleException( $error ); + }; +} + +sub ragUpdate{ + my $error; + + try { + my $servers_list = production_helper::getAllProdServers(); + + my $current_time = time(); + + foreach my $server (@$servers_list){ + # Update current server status + serverUpdate( $server, $current_time ); + } + + production_helper::checkStateChange( $nodes_list ); + + return ajax_data_response( + message => 'Successful.' + ); + } catch($error){ + return handleException( $error ); + }; +} + +# Update server and set colour as per time passed +sub serverUpdate{ + my ($server, $current_time) = @_; + + my $amber_threshold = 3*60; # 3 mins + my $error_threshold = 5*60; # 5 mins + + my $time_diff_in_sec = $current_time - $server->{last_active}; + + if($time_diff_in_sec >= $amber_threshold && $time_diff_in_sec < $error_threshold){ + #update Amber status + production_helper::updateServer($server->{id}, $AMBER_COLOR); + production_logs_helper::addLogEntry($server->{id}, 'SERVER_TIMEOUT', 'Timeout Warning'); + }elsif($time_diff_in_sec > $error_threshold){ + #update Error status + production_helper::updateServer($server->{id}, $RED_COLOR); + production_logs_helper::addLogEntry($server->{id}, 'SERVER_TIMEOUT', 'Timeout Critical'); + } +} + +sub saveConfig{ + my $error; + + try { + my $servers_list = config->{servers_list}; + + foreach my $server_id in (@$servers_list){ + # Register servers in the table and mark them down initially + my $registration_id = production_helper::registerServer($server_id, $RED_COLOR); + } + + return ajax_data_response( + message => 'Successful.' + ); + } catch($error){ + return handleException( $error ); + }; +} + +# Update file transfer information in channels and log table +sub updateFileTransferStatus{ + my $error; + + try { + my $hub_id = param('hubid'); + my $channel_id = param('channelid'); + my $post_params = params(); + my $transfer_status = $post_params->{'transfer_status'}; + my $transfer_msg = $post_params->{'transfer_msg'}; + my $file_name = $post_params->{'file_name'}; + my $file_size = $post_params->{'file_size'}; + + # add file transfer info in log. Size is in MBs + otp_logs_helper::addLogEntry($hub_id, $transfer_status, $transfer_msg, $channel_id, $file_name, $file_size); + + # update filename field in channels table + otp_helper::updateChannelTransferActivity($hub_id, $channel_id, $transfer_status, $file_name); + + return ajax_data_response( + message => 'Successful.' + ); + } catch($error){ + return handleException( $error ); + }; +} + +1; diff --git a/lib/production/production_helper.pm b/lib/production/production_helper.pm new file mode 100644 index 0000000..f2f112f --- /dev/null +++ b/lib/production/production_helper.pm @@ -0,0 +1,96 @@ +package production_helper; + +use Template; +use Dancer2 appname => 'ProdDashboard'; +use Data::Dumper; +use TryCatch; +use production::dao::production_db; +use email::email_helper; +use JSON qw(encode_json decode_json); +use strict; +use warnings; + +sub registerServer { + my ($server_id, $status) = @_; + + my $res = production_db::registerServer($server_id, $status); + + return 1; +} + +sub getAllProdServers { + my $servers_list = production_db::getAllProdServers(); + + return $servers_list; +} + + +sub updateServer { + my ($server_id, $status) = @_; + + my $servers_id = production_db::updateServer($server_id, $status); + + return $servers_id; +} + +# Check if status for any node/channel is changed from previous state, send emails +sub checkStateChange{ + my $nodes_list = shift; + + foreach my $node (@$nodes_list){ + # send emails for hub status change + sendHubMasterEmails( $node ); + + # send emails for any status change in hub channels + sendHubChannelEmails( $node ); + } +} + +# send emails for hub status change +sub sendHubMasterEmails{ + my $node = shift; + my $subject = ''; + + if( $node->{status} != 0 && $node->{status} != $node->{last_status} ){ + # send email + if( $node->{status} == 3 ){ + $subject = "OTP Node $node->{hub_id}: $node->{hub_name} has gone RED"; + } + + if( $node->{status} == 1 ){ + $subject = "OTP Node $node->{hub_id}: $node->{hub_name} has reverted to GREEN"; + } + + if( $subject ne '' ){ + email_helper::sendMail(config->{sender_email}, config->{admin_email}, $subject, $subject); + } + } +} + +# send emails for any status change in hub channels +sub sendHubChannelEmails{ + my $node = shift; + my $subject = ''; + + my $node_channels_list = getAllChannels($node->{hub_id}); + + foreach my $channel (@$node_channels_list){ + if( $channel->{status} != 0 && $channel->{status} != $channel->{last_status} ){ + # send channel fail email + if( $channel->{status} == 3 ){ + $subject = "OTP Channel $channel->{channel_id} in Node $node->{hub_id}: $node->{hub_name} has gone RED"; + } + + if( $channel->{status} == 1 ){ + $subject = "OTP Channel $channel->{channel_id} in Node $node->{hub_id}: $node->{hub_name} has reverted to GREEN"; + } + + if( $subject ne '' ){ + email_helper::sendMail(config->{sender_email}, config->{admin_email}, $subject, $subject); + } + } + } +} + + +1; diff --git a/lib/production/production_logs_helper.pm b/lib/production/production_logs_helper.pm new file mode 100644 index 0000000..8d6990b --- /dev/null +++ b/lib/production/production_logs_helper.pm @@ -0,0 +1,18 @@ +package production_logs_helper; + +use Template; +use Dancer2 appname => 'dashboard'; +use Data::Dumper; +use TryCatch; +use production::dao::production_db; + +sub addLogEntry { + my ($server_number, $activity, $message) = @_; + + my $record_id = production_db::addLogEntry($server_number, $activity, $message); + + return $record_id; +} + + +1; diff --git a/lib/utils/Mailer.pm b/lib/utils/Mailer.pm new file mode 100644 index 0000000..094b39d --- /dev/null +++ b/lib/utils/Mailer.pm @@ -0,0 +1,87 @@ +package Mailer; + +use Net::SMTP; +use Authen::SASL; +use Time::Local; +use Data::Dumper; + +###################################################################### +# send_mail +# +# param 1 - from address +# param 2 - either to address, or array of to addresses (use , to to multi) +# param 3 - body of mail +# param 4 - subject +###################################################################### + sub send_mail + { + my ($from, $to, $body, $subject, $msg); + my ($SMTP_HOST, $smtp); + my (@to_addr); + + $from = $_[0]; + $to = $_[1]; + $body = $_[2]; + $subject = $_[3]; + + $SMTP_HOST = 'smtp.mailgun.org'; + + # convert the list of to address to array + @to_addr = split(',', $to); + + $msg = "MIME-Version: 1.0\n" + . "From: $from\n" + . "To: " . join(';', @to_addr) . "\n" + . "Subject: $subject\n\n" # Double \n + . $body; + + # + # Open a SMTP session + # + $smtp = Net::SMTP->new( $SMTP_HOST, + Hello => 'oliver.solutions', + Debug => 0, # Change to a 1 to turn on debug messages + ); + + if(!defined($smtp) || !($smtp)) + { + print "SMTP ERROR: Unable to open smtp session.\n"; + + return 0; + } + + $smtp->auth('postmaster@oliver.solutions', 'a989aff7fdb39352a0b7b6e3ee4794ed'); + $smtp->banner(); + $smtp->domain(); + + # + # Pass the 'from' email address, exit if error + # + if (! ($smtp->mail( $from ) ) ) + { + return 0; + } + + # + # Pass the recipient address(es) + # + foreach (@to_addr) { + if (! ($smtp->recipient( $_ ))) + { + return 0; + } + } + + # + # Send the message + # + $smtp->data( $msg ); + + $smtp->quit; + + return 1; + } + +# must have this at the end for a require +1; + diff --git a/lib/utils/ajax/response_helper.pm b/lib/utils/ajax/response_helper.pm new file mode 100644 index 0000000..de72374 --- /dev/null +++ b/lib/utils/ajax/response_helper.pm @@ -0,0 +1,23 @@ +package utils::ajax::response_helper; + +use strict; +use warnings; +use JSON qw(to_json); + +use Exporter qw(import); +our @EXPORT = qw(ajax_error_response ajax_data_response); + +sub ajax_error_response { + return ajax_json_response('error', {@_}); +} + +sub ajax_data_response { + return ajax_json_response('data', {@_}); +} + +sub ajax_json_response { + my ($root, $data) = @_; + return to_json({($root => $data)}, { allow_blessed => 1, convert_blessed => 1 }); +} + +1; diff --git a/lib/utils/support/WhiteListSanitizer.pm b/lib/utils/support/WhiteListSanitizer.pm new file mode 100644 index 0000000..6727c87 --- /dev/null +++ b/lib/utils/support/WhiteListSanitizer.pm @@ -0,0 +1,62 @@ +package WhiteListSanitizer; + +use strict; +use warnings; +use HTML::Scrubber; + +our @default_allowed_tags = qw(strong em b i p code pre tt samp kbd var sub + sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol + li dl dt dd abbr acronym a img blockquote del ins); + +our @default_allowed_attributes = qw(href src width height alt cite datetime + title class name xml:lang abbr); + +sub new { + my ($class, @args) = @_; + my $self = bless {}, $class; + return $self->_init(@args); +} + +sub _init { + my ($self) = @_; + return $self; +} + +sub _scrubber { + my $self = shift; + return $self->{_scrubber} //= HTML::Scrubber->new; +} + +sub _allowed_tags { + my ($self, $options) = shift; + $options->{tags} || [@default_allowed_tags]; +} + +sub _allowed_attributes { + my ($self, $options) = shift; + $options->{attributes} || [@default_allowed_attributes]; +} + +sub _configure_scrubber { + my ($self, $options) = @_; + + $self->_scrubber->allow(map {$_ => 1} @{$self->_allowed_tags($options)}); + $self->_scrubber->default( + 0 => { # default rule, deny all tags + '*' => 0, # default rule, deny all attributes + map { $_ => 1 } @{$self->_allowed_attributes($options)} + } + ); +} + +sub sanitize { + my ($self, $html, %options) = @_; + + return unless defined $html; + return $html if $html eq ""; + + $self->_configure_scrubber(\%options); + return $self->_scrubber->scrub($html); +} + +1; diff --git a/lib/utils/support/action_helper.pm b/lib/utils/support/action_helper.pm new file mode 100644 index 0000000..752c712 --- /dev/null +++ b/lib/utils/support/action_helper.pm @@ -0,0 +1,75 @@ +package utils::support::action_helper; + +use Dancer2 appname => 'ProdDashboard'; +use Carp; +use TryCatch; +use Scalar::Util qw(blessed); +use Moo; + +use Dancer2::Plugin::Deferred; +use utils::ajax::response_helper; +use utils::support::WhiteListSanitizer; + +use Exporter qw(import); +our @EXPORT = qw( + redirect_to + sanitize + handleException +); +our @EXPORT_OK = qw(); + +sub sanitize { + return WhiteListSanitizer->new->sanitize(@_); +} + +sub redirect_to { + my ($path, %args) = @_; + + while (my ($key, $value) = each %args) { + deferred $key => $value; + } + + return redirect $path; +} + +sub handleException{ + my ($error, $target) = @_; + + my $msg; + + # check fo Moo based error objects + if (blessed $error){ + + if ( $error->isa('errors::AccessDenied') or $error->isa('errors::ActivityDenied') ) { + $msg = 'Access to this resource has been denied.'; + } + elsif ($error->isa('errors::dbError') and ($error->type() eq 'UniqueViolation')) { + $msg = "Record already exists." + } + elsif ($error->isa('errors::dbError')) { + $msg = "Your data was not saved successfully! Please try again later." + } + else{ + $msg = $error->{message}; + } + + if(defined $error->{target}){ + $target = $error->{target}; + } + } + else{ + + $msg = 'Something went wrong.'; + } + if(defined $target && $target ne ""){ + deferred error => $msg; + redirect $target; + } + else{ + return ajax_error_response( + message => $msg + ); + } +} + +1; diff --git a/lib/utils/support/moo_sanitize.pm b/lib/utils/support/moo_sanitize.pm new file mode 100644 index 0000000..bea02d3 --- /dev/null +++ b/lib/utils/support/moo_sanitize.pm @@ -0,0 +1,82 @@ +package utils::support::moo_sanitize; + +use strict; +use warnings; +use Carp; +use Scalar::Util qw(blessed); +use utils::support::WhiteListSanitizer; + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT = qw(sanitizes sanitize); + +my %THINGS = (); +my $_sanitizer; + +sub sanitizer { + return $_sanitizer //= WhiteListSanitizer->new; +} + +sub sanitizes { + my ($fields, %args) = @_; + my $caller = caller; + + my $thing = $THINGS{$caller}; + $fields = [$fields] if ref($fields) ne 'ARRAY'; + my $sanitize_options = %args ? \%args : 1; + + foreach my $field (@$fields) { + $thing->{fields}->{$field} = $sanitize_options; + } + + return; +} + +sub sanitize { + my $self = shift; + my $class = blessed $self; + + my $thing = $THINGS{$class}; + while (my ($field, $options) = each %{$thing->{fields}}) { + my $value = $self->$field; + + my %santize_options; + if (ref($options) eq "HASH") { + %santize_options = %$options; + } + + $self->$field(sanitizer->sanitize($value, %santize_options)); + } + + return; +} + +sub import { + my $class = shift; + my $caller = caller; + + my $thing = $THINGS{$caller} = { + fields => {} + }; + + no strict 'refs'; + no warnings 'redefine'; + + my $import = $caller->can('import'); + + *{"$caller\::import"} = sub { + foreach my $field (keys %{$thing->{fields}}) { + if (not $caller->can($field)) { + croak "Field: $field is not available on $caller"; + } + } + + if ($import) { + $import->(@_); + } + }; + + __PACKAGE__->export_to_level(1, @_); +} + +1; diff --git a/lib/utils/support/moo_validations.pm b/lib/utils/support/moo_validations.pm new file mode 100644 index 0000000..c063a7a --- /dev/null +++ b/lib/utils/support/moo_validations.pm @@ -0,0 +1,341 @@ +package utils::support::moo_validations; + +use strict; +use warnings; +use Carp; +use Scalar::Util qw(blessed); +use Scalar::Util::Numeric qw(:all); +use Switch; + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT = qw(validates is_valid errors); +our @EXPORT_OK = qw(register_validator); + +my %VALIDATORS = ( + presence => \&_presense_validator, + numericality => \&_numericality_validator, + length => \&_length_validator, + #with => \&_with_validator; +); + +my %THINGS = (); + +sub validates { + my ($fields, %args) = @_; + my $caller = caller; + + my $thing = $THINGS{$caller}; + + $fields = [$fields] if ref($fields) ne 'ARRAY'; + + my $all_on = delete $args{on}; + my $all_message = delete $args{message}; + my $all_allow_blank = delete $args{allow_blank}; + + foreach my $field (@$fields) { + + my $field_validators = $thing->{fields}->{$field} //= []; + + while (my ($validator, $options) = each %args) { + + if (not exists $VALIDATORS{$validator}) { + croak "There is no validator registered with name $validator"; + } + + if (not ref($options) eq 'HASH') { + my $option = $options; + $options = {}; + if (ref($option) eq 'ARRAY') { + $options->{in} = $option; + } else { + $options->{with} = $option; + } + } + + my $on = delete $options->{on} || $all_on || 'all'; + my $message = delete $options->{message} || $all_message; + my $allow_blank = delete $options->{allow_blank} || $all_allow_blank; + + my $validation_options = { + on => $on, + message => $message, + allow_blank => $allow_blank + }; + + push @$field_validators, { + validator => $VALIDATORS{$validator}, + args => $options, + options => $validation_options + }; + + } + + } + + return; +} + +sub is_valid { + my ($self, @args) = @_; + my $class = blessed $self; + + if (@args % 2 != 0) { + my ($field, %args) = @args; + return _validate_field($self, $field, %args); + } + + my $thing = $THINGS{$class}; + + my $valid = 1; + $self->{_validation_errors} = {}; + + foreach my $field (keys %{$thing->{fields}}) { + my $field_valid = _validate_field($self, $field, @args); + $valid &&= $field_valid; + } + + return $valid; +} + +sub _validate_field { + my ($self, $field, %args) = @_; + my $class = blessed $self; + + my $thing = $THINGS{$class}; + my $field_valid = 1; + my $for = $args{for} || 'all'; + + my $field_validators = $thing->{fields}->{$field} // []; + + foreach my $validation (@$field_validators) { + + my $validator = $validation->{validator}; + my $validator_args = $validation->{args}; + my $options = $validation->{options}; + + my $on = $options->{on}; + my $message = $options->{message}; + my $allow_blank = $options->{allow_blank}; + + my $validator_messages = []; + + next unless ($on eq 'all' || $on eq $for); + + my $field_value = $self->$field; + + next if ($allow_blank && (not defined $field_value || $field_value eq "")); + + my $valid = $validator->( + $self->$field, + %$validator_args, + messages => $validator_messages + ); + + if (not $valid) { + $self->errors($field => $message || $validator_messages); + $field_valid = 0; + } + } + + return $field_valid; +} + +sub errors { + my ($self, %args) = @_; + my $class = blessed $self; + + $self->{_validation_errors} //= {}; + + if (keys %args == 0) { + return $self->{_validation_errors}; + } + + while (my ($field, $error_messages) = each %args) { + my $field_errors = $self->{_validation_errors}->{$field} //= []; + $error_messages = [$error_messages] if ref($error_messages) ne 'ARRAY'; + push @$field_errors, @$error_messages; + } + + return; +} + +sub register_validator { + my ($name, $sub) = @_; + + if (not ref($sub) eq 'CODE') { + croak 'Validator subroutine must be a CODE ref'; + } + + if (exists $VALIDATORS{$name}) { + carp "A Validator subroutine with name $name already exists"; + } + + $VALIDATORS{$name} = $sub; + + return; +} + +sub _presense_validator { + my ($value, %config) = @_; + + my $required = $config{with} || $config{is} || 1; + my $present = defined $value && $value ne ""; + my $pass = $required == $present; + + if (not $pass) { + push @{$config{messages}}, " must be present."; + } + + return $pass; +} + +sub _numericality_validator { + my ($value, %config) = @_; + + my $messages = delete $config{messages}; + my $only_integer = !! delete $config{only_integer}; + + if ($only_integer) { + unless (isint($value)) { + push @$messages, " is not an integer"; + return 0; + } + } else { + my $numericality = !! delete $config{with} || 1; + unless (isnum($value) == $numericality) { + push @$messages, $numericality ? " is not a number" : " is a number"; + return 0; + } + } + + my %numericality_options = %config; + my $pass = 1; + + while (my ($option, $option_value) = each %numericality_options) { + + unless ($option eq 'odd' || $option eq 'even') { + croak "$option must be a number" if (not isnum($option_value)); + } + + switch ($option) { + case 'greater_than' { + unless ($value > $option_value) { + push @$messages, " is not greater than $option_value"; + $pass = 0; + } + } + case 'greater_than_or_equal_to' { + unless ($value >= $option_value) { + push @$messages, " is not greater than or equal to $option_value"; + $pass = 0; + } + } + case 'equal_to' { + unless ($value == $option_value) { + push @$messages, " is not equal to $option_value"; + $pass = 0; + } + } + case 'less_than' { + unless ($value < $option_value) { + push @$messages, " is not less than $option_value"; + $pass = 0; + } + } + case 'less_than_or_equal_to' { + unless ($value <= $option_value) { + push @$messages, " is not less than or equal to $option_value"; + $pass = 0; + } + } + case 'odd' { + unless ($value % 2 == 1) { + push @$messages, " is not a odd number"; + $pass = 0; + } + } + case 'even' { + unless ($value % 2 == 0) { + push @$messages, " is not a even number"; + $pass = 0; + } + } + } + } + + return $pass; +} + +sub _length_validator { + my ($value, %config) = @_; + + my $messages = delete $config{messages}; + + my ($option) = keys %config; + my $option_value = $config{$option}; + my $value_length = length $value; + my $pass = 1; + + switch ($option) { + case ['within', 'in'] { + my ($min, $max) = @$option_value; + if ($value_length < $min || $value_length > $max) { + push @$messages, " is the wrong length (should be between $min and $max characters)"; + $pass = 0; + } + } + case 'is' { + if ($value_length != $option_value) { + push @$messages, " is the wrong length (should be $option_value characters)"; + $pass = 0; + } + } + case 'minimum' { + if ($value_length < $option_value) { + push @$messages, " is too short (minimum is $option_value characters)"; + $pass = 0; + } + } + case 'maxmimum' { + if ($value_length > $option_value) { + push @$messages, " is too long (maximum is $option_value characters)"; + $pass = 0; + } + } + } + + return $pass; +} + +sub import { + my $class = shift; + my $caller = caller; + + my $thing = $THINGS{$caller} = { + fields => {} + }; + + no strict 'refs'; + no warnings 'redefine'; + + my $import = $caller->can('import'); + + *{"$caller\::import"} = sub { + # Attempt to check at compile time + # if validate fields exists on object + foreach my $field (keys %{$thing->{fields}}) { + if (not $caller->can($field)) { + croak "Field: $field is not available on $caller"; + } + } + + if ($import) { + $import->(@_); + } + }; + + __PACKAGE__->export_to_level(1, @_); +} + +1; diff --git a/prun.sh b/prun.sh new file mode 100644 index 0000000..f96f6a5 --- /dev/null +++ b/prun.sh @@ -0,0 +1 @@ +plackup -s Starman bin/app.psgi diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..c2f4032 --- /dev/null +++ b/public/404.html @@ -0,0 +1,18 @@ + + + + + + Error 404 + + + +

Error 404

+
+

Page Not Found

Sorry, this is the void.

+
+ + + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..d84ebbc --- /dev/null +++ b/public/500.html @@ -0,0 +1,18 @@ + + + + + + Error 500 + + + +

Error 500

+
+

Internal Server Error

Wooops, something went wrong

+
+ + + diff --git a/public/css/error.css b/public/css/error.css new file mode 100644 index 0000000..8a5e831 --- /dev/null +++ b/public/css/error.css @@ -0,0 +1,85 @@ +body { + font-family: Lucida,sans-serif; +} + +h1 { + color: #AA0000; + border-bottom: 1px solid #444; +} + +h2 { color: #444; } + +pre { + font-family: "lucida console","monaco","andale mono","bitstream vera sans mono","consolas",monospace; + font-size: 12px; + border-left: 2px solid #777; + padding-left: 1em; +} + +footer { + font-size: 10px; +} + +span.key { + color: #449; + font-weight: bold; + width: 120px; + display: inline; +} + +span.value { + color: #494; +} + +/* these are for the message boxes */ + +pre.content { + background-color: #eee; + color: #000; + padding: 1em; + margin: 0; + border: 1px solid #aaa; + border-top: 0; + margin-bottom: 1em; + overflow-x: auto; +} + +div.title { + font-family: "lucida console","monaco","andale mono","bitstream vera sans mono","consolas",monospace; + font-size: 12px; + background-color: #aaa; + color: #444; + font-weight: bold; + padding: 3px; + padding-left: 10px; +} + +table.context { + border-spacing: 0; +} + +table.context th, table.context td { + padding: 0; +} + +table.context th { + color: #889; + font-weight: normal; + padding-right: 15px; + text-align: right; +} + +.errline { + color: red; +} + +pre.error { + background: #334; + color: #ccd; + padding: 1em; + border-top: 1px solid #000; + border-left: 1px solid #000; + border-right: 1px solid #eee; + border-bottom: 1px solid #eee; +} + diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..80f94eb --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,189 @@ + +body { +margin: 0; +margin-bottom: 25px; +padding: 0; +background-color: #ddd; +background-image: url("/images/perldancer-bg.jpg"); +background-repeat: no-repeat; +background-position: top left; + +font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana"; +font-size: 13px; +color: #333; +} + +h1 { +font-size: 28px; +color: #000; +} + +a {color: #03c} +a:hover { +background-color: #03c; +color: white; +text-decoration: none; +} + +#page { +background-color: #ddd; +width: 750px; +margin: auto; +margin-left: auto; +padding-left: 0px; +margin-right: auto; +} + +#content { +background-color: white; +border: 3px solid #aaa; +border-top: none; +padding: 25px; +width: 500px; +} + +#sidebar { +float: right; +width: 175px; +} + +#header, #about, #getting-started { +padding-left: 75px; +padding-right: 30px; +} + + +#header { +background-image: url("/images/perldancer.jpg"); +background-repeat: no-repeat; +background-position: top left; +height: 64px; +} +#header h1, #header h2 {margin: 0} +#header h2 { +color: #888; +font-weight: normal; +font-size: 16px; +} + +#about h3 { +margin: 0; +margin-bottom: 10px; +font-size: 14px; +} + +#about-content { +background-color: #ffd; +border: 1px solid #fc0; +margin-left: -11px; +} +#about-content table { +margin-top: 10px; +margin-bottom: 10px; +font-size: 11px; +border-collapse: collapse; +} +#about-content td { +padding: 10px; +padding-top: 3px; +padding-bottom: 3px; +} +#about-content td.name {color: #555} +#about-content td.value {color: #000} + +#about-content.failure { +background-color: #fcc; +border: 1px solid #f00; +} +#about-content.failure p { +margin: 0; +padding: 10px; +} + +#getting-started { +border-top: 1px solid #ccc; +margin-top: 25px; +padding-top: 15px; +} +#getting-started h1 { +margin: 0; +font-size: 20px; +} +#getting-started h2 { +margin: 0; +font-size: 14px; +font-weight: normal; +color: #333; +margin-bottom: 25px; +} +#getting-started ol { +margin-left: 0; +padding-left: 0; +} +#getting-started li { +font-size: 18px; +color: #888; +margin-bottom: 25px; +} +#getting-started li h2 { +margin: 0; +font-weight: normal; +font-size: 18px; +color: #333; +} +#getting-started li p { +color: #555; +font-size: 13px; +} + +#search { +margin: 0; +padding-top: 10px; +padding-bottom: 10px; +font-size: 11px; +} +#search input { +font-size: 11px; +margin: 2px; +} +#search-text {width: 170px} + +#sidebar ul { +margin-left: 0; +padding-left: 0; +} +#sidebar ul h3 { +margin-top: 25px; +font-size: 16px; +padding-bottom: 10px; +border-bottom: 1px solid #ccc; +} +#sidebar li { +list-style-type: none; +} +#sidebar ul.links li { +margin-bottom: 5px; +} + +h1, h2, h3, h4, h5 { +font-family: sans-serif; +margin: 1.2em 0 0.6em 0; +} + +p { +line-height: 1.5em; +margin: 1.6em 0; +} + +code, .filepath, .app-info { + font-family: 'Andale Mono', Monaco, 'Liberation Mono', 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', monospace; +} + +#footer { +clear: both; +padding-top: 2em; +text-align: center; +padding-right: 160px; +font-family: sans-serif; +font-size: 10px; +} diff --git a/public/custom/default/app_dashboard.css b/public/custom/default/app_dashboard.css new file mode 100644 index 0000000..a7cfc7b --- /dev/null +++ b/public/custom/default/app_dashboard.css @@ -0,0 +1,63 @@ + table { + color: #333; /* Lighten up font color */ + font-family: Helvetica, Arial, sans-serif; /* Nicer font */ + width: 690px; + border-collapse: + collapse; border-spacing: 0; + margin: auto; +} + +td, th { border: 1px solid #CCC; height: 30px; } /* Make cells a bit taller */ + +th { + background: #F3F3F3; /* Light grey background */ + font-weight: bold; /* Make sure they're bold */ +} + +td { + background: #FAFAFA; /* Lighter grey background */ + text-align: center; /* Center our text */ +} + +td.red{ + background: red; +} + +td.green{ + background: green; +} + +td.amber{ + background: #FFC200; +} + +.form-control.datefield{ + width: 120px; + background: #fafafa none repeat scroll 0 0; + border: none; + font-family: Helvetica,Arial,sans-serif; + font-size: 14px; + color: #333; +} + +.container-fluid { + height: 100%; +} + +.clickable-row{ + cursor: pointer; +} + +.button{ + display: block; + width: 115px; + height: 25px; + background: #b2b2b2; + padding: 10px; + text-align: center; + border-radius: 5px; + color: white; + font-weight: bold; + margin: 20px auto auto; + text-decoration: none; +} \ No newline at end of file diff --git a/public/dispatch.cgi b/public/dispatch.cgi new file mode 100644 index 0000000..706ba0c --- /dev/null +++ b/public/dispatch.cgi @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +BEGIN { $ENV{DANCER_APPHANDLER} = 'PSGI';} +use Dancer2; +use FindBin '$RealBin'; +use Plack::Runner; + +# For some reason Apache SetEnv directives don't propagate +# correctly to the dispatchers, so forcing PSGI and env here +# is safer. +set apphandler => 'PSGI'; +set environment => 'production'; + +my $psgi = path($RealBin, '..', 'bin', 'app.psgi'); +die "Unable to read startup script: $psgi" unless -r $psgi; + +Plack::Runner->run($psgi); diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi new file mode 100644 index 0000000..ad42deb --- /dev/null +++ b/public/dispatch.fcgi @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +BEGIN { $ENV{DANCER_APPHANDLER} = 'PSGI';} +use Dancer2; +use FindBin '$RealBin'; +use Plack::Handler::FCGI; + +# For some reason Apache SetEnv directives don't propagate +# correctly to the dispatchers, so forcing PSGI and env here +# is safer. +set apphandler => 'PSGI'; +set environment => 'production'; + +my $psgi = path($RealBin, '..', 'bin', 'app.psgi'); +my $app = do($psgi); +die "Unable to read startup script: $@" if $@; +my $server = Plack::Handler::FCGI->new(nproc => 5, detach => 1); + +$server->run($app); diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..96c7465 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/images/perldancer-bg.jpg b/public/images/perldancer-bg.jpg new file mode 100644 index 0000000..6ee6b77 Binary files /dev/null and b/public/images/perldancer-bg.jpg differ diff --git a/public/images/perldancer.jpg b/public/images/perldancer.jpg new file mode 100644 index 0000000..3f9e718 Binary files /dev/null and b/public/images/perldancer.jpg differ diff --git a/public/javascripts/homepage/homepage.js b/public/javascripts/homepage/homepage.js new file mode 100644 index 0000000..747c847 --- /dev/null +++ b/public/javascripts/homepage/homepage.js @@ -0,0 +1,9 @@ +$( document ).ready(function() { + + $(".clickable-row").click(function() { + // window.location = $(this).data("href"); + }); + +}); + + diff --git a/public/javascripts/jquery.js b/public/javascripts/jquery.js new file mode 100644 index 0000000..49990d6 --- /dev/null +++ b/public/javascripts/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("