OMG-4586 - Initial production dashboard push
This commit is contained in:
commit
1473e39784
62 changed files with 3045 additions and 0 deletions
0
.dancer
Normal file
0
.dancer
Normal file
24
MANIFEST
Normal file
24
MANIFEST
Normal file
|
|
@ -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
|
||||
17
MANIFEST.SKIP
Normal file
17
MANIFEST.SKIP
Normal file
|
|
@ -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-
|
||||
26
Makefile.PL
Normal file
26
Makefile.PL
Normal file
|
|
@ -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 <youremail@example.com>},
|
||||
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-*' },
|
||||
);
|
||||
10
bin/app.psgi
Normal file
10
bin/app.psgi
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
|
||||
use ProdDashboard;
|
||||
ProdDashboard->to_app;
|
||||
|
||||
69
config.yml
Normal file
69
config.yml
Normal file
|
|
@ -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
|
||||
11
cpanfile
Normal file
11
cpanfile
Normal file
|
|
@ -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";
|
||||
};
|
||||
1
dbscripts/auto/backup.sh
Normal file
1
dbscripts/auto/backup.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
PGPASSWORD=oliver99 pg_dump proddashboard -Fc -U postgres -h localhost -f prod_dashboard_temp.sql
|
||||
329
dbscripts/auto/db_deploy.pl
Normal file
329
dbscripts/auto/db_deploy.pl
Normal file
|
|
@ -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
|
||||
23
dbscripts/auto/deploy_table_create.sql
Normal file
23
dbscripts/auto/deploy_table_create.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
/************************************************************
|
||||
* Author : Geetha Nair<Geethanair@oliver-marketing.com
|
||||
* Date : 28/April/2015
|
||||
* Description : Create a table called deploy that holds details of db scripts run on master
|
||||
*************************************************************/
|
||||
|
||||
|
||||
-- Table: "deploy"
|
||||
|
||||
-- DROP TABLE "deploy";
|
||||
|
||||
CREATE TABLE "deploy"
|
||||
(
|
||||
"id" serial NOT NULL,
|
||||
"sprint_id" integer,
|
||||
"script_id" integer,
|
||||
"status" boolean,
|
||||
CONSTRAINT "deploy_pkey" PRIMARY KEY ("id")
|
||||
)
|
||||
WITH (
|
||||
OIDS=FALSE
|
||||
);
|
||||
1
dbscripts/auto/init_deploy.sh
Normal file
1
dbscripts/auto/init_deploy.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
PGPASSWORD=oliver99 psql -U postgres -d proddashboard -h localhost -1 -f deploy_table_create.sql
|
||||
1
dbscripts/auto/restore.sh
Normal file
1
dbscripts/auto/restore.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
PGPASSWORD=oliver99 pg_restore -U postgres -h localhost -n public --dbname=proddashboard -c -1 prod_dashboard_temp.sql
|
||||
113
dbscripts/auto/ss_deploy.pl
Normal file
113
dbscripts/auto/ss_deploy.pl
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB deployment automation(support scripts)
|
||||
# must always be run from the target auto folder.
|
||||
#
|
||||
# always backup DB before a deploy
|
||||
# pg_dump dashboard -Fc -U postgres -h localhost > 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
|
||||
24
dbscripts/s1/deploy/100_db_4586_prod_servers_create.sql
Normal file
24
dbscripts/s1/deploy/100_db_4586_prod_servers_create.sql
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/********************************************************************************************
|
||||
* Author : Faisal Zia<faisal.zia@jintech.com
|
||||
* Date : 02/11/2018
|
||||
* Description : Create production servers table
|
||||
* Jira Ticket : https://oliveruk.atlassian.net/browse/OMG-4586
|
||||
*********************************************************************************************/
|
||||
|
||||
-- Table: public.prod_servers
|
||||
|
||||
DROP TABLE IF EXISTS public.prod_servers;
|
||||
|
||||
CREATE TABLE public.prod_servers
|
||||
(
|
||||
id serial,
|
||||
server_id character varying(255),
|
||||
status integer, -- defines Servers RAG status: 1:Green, 2:Amber, 3:Red
|
||||
last_active timestamp with time zone,
|
||||
CONSTRAINT prod_servers_pkey PRIMARY KEY (id)
|
||||
)
|
||||
WITH (
|
||||
OIDS=FALSE
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN public.prod_servers.status IS 'defines Servers RAG status: 1:Green, 2:Amber, 3:Red';
|
||||
44
dbscripts/s1/deploy/101_db_4586_register_prod_servers.sql
Normal file
44
dbscripts/s1/deploy/101_db_4586_register_prod_servers.sql
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/********************************************************************************************
|
||||
* Author : Faisal Zia<faisal.zia@jintech.com
|
||||
* Date : 02/11/2018
|
||||
* Description : Register production servers
|
||||
* Jira Ticket : https://oliveruk.atlassian.net/browse/OMG-4586
|
||||
*********************************************************************************************/
|
||||
|
||||
-- Function: register_prod_servers(character varying, integer, integer)
|
||||
|
||||
DROP FUNCTION IF EXISTS register_prod_servers(character varying, integer, integer);
|
||||
|
||||
CREATE OR REPLACE FUNCTION register_prod_servers(
|
||||
server_num character varying,
|
||||
server_status integer)
|
||||
RETURNS integer AS
|
||||
$BODY$
|
||||
DECLARE
|
||||
lastid int;
|
||||
BEGIN
|
||||
-- insert a new record into prod_servers
|
||||
WITH upsert AS (UPDATE "public"."prod_servers"
|
||||
SET
|
||||
status = server_status,
|
||||
last_active =
|
||||
CASE WHEN server_status = 1
|
||||
THEN now()
|
||||
ELSE last_active
|
||||
END
|
||||
WHERE server_id = server_num
|
||||
RETURNING server_id)
|
||||
INSERT INTO "public"."prod_servers" ( server_id, last_active, status )
|
||||
SELECT server_num, now(), server_status
|
||||
WHERE NOT EXISTS (SELECT 1 FROM upsert WHERE server_id = server_num)
|
||||
RETURNING server_id INTO lastid;
|
||||
IF lastid IS NULL THEN
|
||||
lastid := server_num;
|
||||
END IF;
|
||||
|
||||
RETURN lastid;
|
||||
|
||||
END;
|
||||
$BODY$
|
||||
LANGUAGE plpgsql VOLATILE;
|
||||
|
||||
30
dbscripts/s1/deploy/102_db_4586_get_all_prod_servers.sql
Normal file
30
dbscripts/s1/deploy/102_db_4586_get_all_prod_servers.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/********************************************************************************************
|
||||
* Author : Faisal Zia<faisal.zia@jintech.com
|
||||
* Date : 02/11/2018
|
||||
* Description : Get list of all production servers
|
||||
* Jira Ticket : https://oliveruk.atlassian.net/browse/OMG-4586
|
||||
*********************************************************************************************/
|
||||
|
||||
-- Function: get_all_prod_servers()
|
||||
|
||||
DROP FUNCTION IF EXISTS get_all_prod_servers();
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_all_prod_servers()
|
||||
RETURNS TABLE(
|
||||
id integer,
|
||||
server_number character varying,
|
||||
last_active double precision,
|
||||
status integer
|
||||
) AS
|
||||
$BODY$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
prodser.id,
|
||||
prodser.server_id,
|
||||
floor(EXTRACT(EPOCH FROM prodser.last_active)),
|
||||
prodser.status
|
||||
FROM "public"."prod_servers" AS prodser;
|
||||
END;
|
||||
$BODY$
|
||||
LANGUAGE plpgsql VOLATILE;
|
||||
24
dbscripts/s1/deploy/103_db_4586_prod_logs_create.sql
Normal file
24
dbscripts/s1/deploy/103_db_4586_prod_logs_create.sql
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*********************************************************************************************************************************
|
||||
* Author : Faisal Zia <faisal.zia@jintech.com>
|
||||
* 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
|
||||
);
|
||||
30
dbscripts/s1/deploy/104_db_4586_create_prod_log_entry.sql
Normal file
30
dbscripts/s1/deploy/104_db_4586_create_prod_log_entry.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/********************************************************************************************
|
||||
* Author : Faisal Zia<faisal.zia@jintech.com>
|
||||
* 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;
|
||||
|
||||
44
environments/development.yml
Normal file
44
environments/development.yml
Normal file
|
|
@ -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"
|
||||
16
environments/production.yml
Normal file
16
environments/production.yml
Normal file
|
|
@ -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
|
||||
19
lib/ProdDashboard.pm
Normal file
19
lib/ProdDashboard.pm
Normal file
|
|
@ -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;
|
||||
BIN
lib/email/.DS_Store
vendored
Normal file
BIN
lib/email/.DS_Store
vendored
Normal file
Binary file not shown.
36
lib/email/email_helper.pm
Normal file
36
lib/email/email_helper.pm
Normal file
|
|
@ -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;
|
||||
7
lib/errors/AccessDenied.pm
Normal file
7
lib/errors/AccessDenied.pm
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package errors::AccessDenied;
|
||||
|
||||
use Moo;
|
||||
|
||||
extends 'errors::Error';
|
||||
|
||||
1;
|
||||
7
lib/errors/ActivityDenied.pm
Normal file
7
lib/errors/ActivityDenied.pm
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package errors::ActivityDenied;
|
||||
|
||||
use Moo;
|
||||
|
||||
extends 'errors::Error';
|
||||
|
||||
1;
|
||||
7
lib/errors/ArgumentError.pm
Normal file
7
lib/errors/ArgumentError.pm
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package errors::ArgumentError;
|
||||
|
||||
use Moo;
|
||||
|
||||
extends 'errors::Error';
|
||||
|
||||
1;
|
||||
24
lib/errors/Error.pm
Normal file
24
lib/errors/Error.pm
Normal file
|
|
@ -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;
|
||||
12
lib/errors/customExceptions.pm
Normal file
12
lib/errors/customExceptions.pm
Normal file
|
|
@ -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;
|
||||
249
lib/errors/dbError.pm
Normal file
249
lib/errors/dbError.pm
Normal file
|
|
@ -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;
|
||||
7
lib/errors/invalidInput.pm
Normal file
7
lib/errors/invalidInput.pm
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package errors::invalidInput;
|
||||
|
||||
use Moo;
|
||||
|
||||
extends 'errors::Error';
|
||||
|
||||
1;
|
||||
7
lib/errors/invalidObject.pm
Normal file
7
lib/errors/invalidObject.pm
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package errors::invalidObject;
|
||||
|
||||
use Moo;
|
||||
|
||||
extends 'errors::Error';
|
||||
|
||||
1;
|
||||
7
lib/errors/lostResource.pm
Normal file
7
lib/errors/lostResource.pm
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package errors::lostResource;
|
||||
|
||||
use Moo;
|
||||
|
||||
extends 'errors::Error';
|
||||
|
||||
1;
|
||||
7
lib/errors/notFound.pm
Normal file
7
lib/errors/notFound.pm
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package errors::notFound;
|
||||
|
||||
use Moo;
|
||||
|
||||
extends 'errors::Error';
|
||||
|
||||
1;
|
||||
34
lib/homepage/homepage_controller.pm
Normal file
34
lib/homepage/homepage_controller.pm
Normal file
|
|
@ -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;
|
||||
160
lib/production/dao/production_db.pm
Normal file
160
lib/production/dao/production_db.pm
Normal file
|
|
@ -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;
|
||||
154
lib/production/production_controller.pm
Normal file
154
lib/production/production_controller.pm
Normal file
|
|
@ -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;
|
||||
96
lib/production/production_helper.pm
Normal file
96
lib/production/production_helper.pm
Normal file
|
|
@ -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;
|
||||
18
lib/production/production_logs_helper.pm
Normal file
18
lib/production/production_logs_helper.pm
Normal file
|
|
@ -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;
|
||||
87
lib/utils/Mailer.pm
Normal file
87
lib/utils/Mailer.pm
Normal file
|
|
@ -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;
|
||||
|
||||
23
lib/utils/ajax/response_helper.pm
Normal file
23
lib/utils/ajax/response_helper.pm
Normal file
|
|
@ -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;
|
||||
62
lib/utils/support/WhiteListSanitizer.pm
Normal file
62
lib/utils/support/WhiteListSanitizer.pm
Normal file
|
|
@ -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;
|
||||
75
lib/utils/support/action_helper.pm
Normal file
75
lib/utils/support/action_helper.pm
Normal file
|
|
@ -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;
|
||||
82
lib/utils/support/moo_sanitize.pm
Normal file
82
lib/utils/support/moo_sanitize.pm
Normal file
|
|
@ -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;
|
||||
341
lib/utils/support/moo_validations.pm
Normal file
341
lib/utils/support/moo_validations.pm
Normal file
|
|
@ -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;
|
||||
1
prun.sh
Normal file
1
prun.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
plackup -s Starman bin/app.psgi
|
||||
18
public/404.html
Normal file
18
public/404.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||
<title>Error 404</title>
|
||||
<link rel="stylesheet" href="/css/error.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Error 404</h1>
|
||||
<div id="content">
|
||||
<h2>Page Not Found</h2><p>Sorry, this is the void.</p>
|
||||
</div>
|
||||
<div id="footer">
|
||||
Powered by <a href="http://perldancer.org/">Dancer2</a>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
18
public/500.html
Normal file
18
public/500.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||
<title>Error 500</title>
|
||||
<link rel="stylesheet" href="/css/error.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Error 500</h1>
|
||||
<div id="content">
|
||||
<h2>Internal Server Error</h2><p>Wooops, something went wrong</p>
|
||||
</div>
|
||||
<div id="footer">
|
||||
Powered by <a href="http://perldancer.org/">Dancer2</a>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
85
public/css/error.css
Normal file
85
public/css/error.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
189
public/css/style.css
Normal file
189
public/css/style.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
63
public/custom/default/app_dashboard.css
Normal file
63
public/custom/default/app_dashboard.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
16
public/dispatch.cgi
Normal file
16
public/dispatch.cgi
Normal file
|
|
@ -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);
|
||||
18
public/dispatch.fcgi
Normal file
18
public/dispatch.fcgi
Normal file
|
|
@ -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);
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/images/perldancer-bg.jpg
Normal file
BIN
public/images/perldancer-bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7 KiB |
BIN
public/images/perldancer.jpg
Normal file
BIN
public/images/perldancer.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
9
public/javascripts/homepage/homepage.js
Normal file
9
public/javascripts/homepage/homepage.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
$( document ).ready(function() {
|
||||
|
||||
$(".clickable-row").click(function() {
|
||||
// window.location = $(this).data("href");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
4
public/javascripts/jquery.js
vendored
Normal file
4
public/javascripts/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
t/001_base.t
Normal file
5
t/001_base.t
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Test::More tests => 1;
|
||||
use_ok 'ProdDashboard';
|
||||
16
t/002_index_route.t
Normal file
16
t/002_index_route.t
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
use ProdDashboard;
|
||||
use Test::More tests => 2;
|
||||
use Plack::Test;
|
||||
use HTTP::Request::Common;
|
||||
use Ref::Util qw<is_coderef>;
|
||||
|
||||
my $app = ProdDashboard->to_app;
|
||||
ok( is_coderef($app), 'Got app' );
|
||||
|
||||
my $test = Plack::Test->create($app);
|
||||
my $res = $test->request( GET '/' );
|
||||
|
||||
ok( $res->is_success, '[GET /] successful' );
|
||||
43
views/homepage/homepage.tt
Normal file
43
views/homepage/homepage.tt
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<%
|
||||
page = "homepage"
|
||||
%>
|
||||
|
||||
<style>
|
||||
.blue_background{
|
||||
background-color: #87CEFA;
|
||||
}
|
||||
</style>
|
||||
|
||||
<% USE date %>
|
||||
|
||||
<script type="text/javascript" src="/javascripts/homepage/homepage.js"></script>
|
||||
|
||||
<meta http-equiv="refresh" content="10; URL=<% domain %>">
|
||||
|
||||
<table class="table-pagecentered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Server Name</th>
|
||||
<th>Last Active</th>
|
||||
<th>RAG Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% FOREACH prod_ser IN servers_list %>
|
||||
<tr class='clickable-row' data-href=''>
|
||||
<td class = "" ><% prod_ser.server_number %></td>
|
||||
<td class = "" >
|
||||
<% date.format(prod_ser.last_active, '%Y-%m-%d %H:%M') %>
|
||||
</td>
|
||||
|
||||
<% IF prod_ser.status == 1 %>
|
||||
<td class='green'> </td>
|
||||
<% ELSIF prod_ser.status == 2 %>
|
||||
<td class='amber'> </td>
|
||||
<% ELSE %>
|
||||
<td class='red'> </td>
|
||||
<% END %>
|
||||
</tr>
|
||||
<% END %>
|
||||
</tbody>
|
||||
</table>
|
||||
148
views/index.tt
Normal file
148
views/index.tt
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
|
||||
<!--
|
||||
Credit goes to the Ruby on Rails team for this page
|
||||
has been heavily based on the default Rails page that is
|
||||
built with a scaffolded application.
|
||||
|
||||
Thanks a lot to them for their work.
|
||||
|
||||
See Ruby on Rails if you want a kickass framework in Ruby:
|
||||
http://www.rubyonrails.org/
|
||||
-->
|
||||
|
||||
<div id="page">
|
||||
<div id="sidebar">
|
||||
<ul id="sidebar-items">
|
||||
<li>
|
||||
<h3>Join the community</h3>
|
||||
<ul class="links">
|
||||
|
||||
<li><a href="http://perldancer.org/">PerlDancer</a></li>
|
||||
<li><a href="http://twitter.com/PerlDancer/">Official Twitter</a></li>
|
||||
<li><a href="https://github.com/PerlDancer/Dancer2/">GitHub Community</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h3>Browse the documentation</h3>
|
||||
|
||||
<ul class="links">
|
||||
<li><a
|
||||
href="https://metacpan.org/pod/Dancer2::Manual">Introduction</a></li>
|
||||
<li><a href="https://metacpan.org/pod/Dancer2::Cookbook">Cookbook</a></li>
|
||||
<li><a
|
||||
href="https://metacpan.org/pod/Dancer2::Tutorial"
|
||||
title="a tutorial to build a small blog engine with Dancer">Tutorial</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h3>Your application's environment</h3>
|
||||
|
||||
<ul>
|
||||
<li>Location: <span class="filepath">/var/www/ProdDashboard</span></li>
|
||||
<li>Template engine: <span class="app-info"><% settings.template %></span></li>
|
||||
<li>Logger: <span class="app-info"><% settings.logger %></span></li>
|
||||
<li>Environment: <span class="app-info"><% settings.environment %></span></li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<div id="header">
|
||||
<h1>Perl is dancing</h1>
|
||||
<h2>You’ve joined the dance floor!</h2>
|
||||
</div>
|
||||
|
||||
<div id="getting-started">
|
||||
<h1>Getting started</h1>
|
||||
<h2>Here’s how to get dancing:</h2>
|
||||
|
||||
<h3><a href="#" id="about_env_link">About your application's environment</a></h3>
|
||||
|
||||
<div id="about-content" style="display: none;">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Perl version</td>
|
||||
<td><span class="app-info"><% perl_version %></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dancer2 version</td>
|
||||
<td><span class="app-info"><% dancer_version %></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Backend</td>
|
||||
<td><span class="app-info"><% settings.apphandler %></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Appdir</td>
|
||||
<td><span class="filepath">/var/www/ProdDashboard</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Template engine</td>
|
||||
<td><span class="app-info"><% settings.template %></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Logger engine</td>
|
||||
<td><span class="app-info"><% settings.logger %></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Running environment</td>
|
||||
<td><span class="app-info"><% settings.environment %></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$('#about_env_link').click(function() {
|
||||
$('#about-content').slideToggle('fast', function() {
|
||||
// ok
|
||||
});
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<h2>Tune your application</h2>
|
||||
|
||||
<p>
|
||||
Your application is configured via a global configuration file,
|
||||
<span class="filepath">config.yml</span> and an "environment" configuration file,
|
||||
<span class="filepath">environments/development.yml</span>. Edit those files if you
|
||||
want to change the settings of your application.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h2>Add your own routes</h2>
|
||||
|
||||
<p>
|
||||
The default route that displays this page can be removed,
|
||||
it's just here to help you get started. The template used to
|
||||
generate this content is located in
|
||||
<span class="filepath">views/index.tt</span>.
|
||||
You can add some routes to <span class="filepath">lib/ProdDashboard.pm</span>.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h2>Enjoy web development again</h2>
|
||||
|
||||
<p>
|
||||
Once you've made your changes, restart your standalone server
|
||||
<span class="filepath">(bin/app.psgi)</span> and you're ready
|
||||
to test your web application.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
54
views/layouts/main.tt
Normal file
54
views/layouts/main.tt
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<%
|
||||
application_name = "Production Dashboard"
|
||||
root_path = request.uri_base
|
||||
%>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=<% settings.charset %>" />
|
||||
|
||||
<title><% application_name %></title>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="shortcut icon" href="/custom/default/images/favicon.ico">
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="/custom/default/app_dashboard.css">
|
||||
|
||||
<script type="text/javascript" src="/javascripts/jquery.js"></script>
|
||||
|
||||
<!--LIBRARY JAVASCRIPT-->
|
||||
<!-- <script type="text/javascript" src="/common/javascript/javascript_v2.js"></script>
|
||||
<script type="text/javascript" src="/common/javascript/jquery/jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript" src="/common/javascript/jquery/jquery-migrate-1.2.1.js"></script>
|
||||
<script type="text/javascript" src="/common/javascript/bootstrap/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="/common/javascript/remoteChained/jquery.chained.remote.min.js"></script>
|
||||
<script type="text/javascript" src="/common/javascript/bootstrapDatePicker/moment-with-locales.js"></script>
|
||||
<script type="text/javascript" src="/common/javascript/bootstrapDatePicker/bootstrap-datetimepicker.js"></script> -->
|
||||
|
||||
<!--CUSTOM JAVASCRIPT-->
|
||||
<!-- <script type="text/javascript" src="/javascripts/navigation/navigation_omg.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/generic/dates_omg.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/generic/password_modal_omg.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/alerts/flash_alert_omg.js"></script> -->
|
||||
|
||||
|
||||
<!-- <link rel="stylesheet" href="/common/css/jquery/jquery-ui.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="/common/css/datePicker/datepicker.min.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="/common/css/datePicker/datepicker3.min.css" type="text/css" media="all" />
|
||||
|
||||
<link rel="stylesheet" href="/css/main_omg.css" type="text/css" media="all" /> -->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<!--START CONTENT-->
|
||||
<% content %>
|
||||
<!--END CONTENT-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue