User Tools

Site Tools


Work example of the pgdumper code

pgagent.pl

pgagent.pl
#!/usr/bin/env perl
 
#-------------
#--- MODEL ---
#-------------
 
package PGagent::Model;
 
use strict;
use warnings;
use File::stat;
use Data::Dumper;
use DBI;
use Mojo::UserAgent;
use POSIX;
use Socket;
 
sub new {
    my ($class, $app, $pghost, $username, $password) = @_;
    my $self = {
        app => $app,
        pghost => $pghost,
        dsn => "dbi:Pg:dbname=postgres;host=$pghost",
        username => $username,
        password => $password,
    };
    bless $self, $class;
    return $self;
}
 
sub username { 
    return shift->{username}; 
}
sub password { 
    return shift->{password}; 
}
sub dsn { 
    return shift->{dsn}; 
}
sub pghost { 
    return shift->{pghost}; 
}
sub app { 
    return shift->{app}; 
}
 
 
sub storeAlive {
    my ($self, $hostname) = @_;
    return undef unless $hostname;
    my $ua = Mojo::UserAgent->new(max_redirects => 2) or return undef;
    my $tx = $ua->get("http://$hostname:3002/hello");
    my $res;
    eval { $res = decode_json($tx->result->body); };
    return 1;# if $res->{'message'} eq 'hello';
    return undef;
}
 
sub storePut {
    my ($self, $datafile, $storename, $storeuser, $storepwd) = @_;
    my $ua = Mojo::UserAgent->new(max_redirects => 5);
    return undef unless $datafile;
    return undef unless $storename;
 
    return undef unless -f $datafile;
    return undef unless -r $datafile;
    return undef unless $self->storeAlive($storename);
 
    $self->app->log->info(" --- Start upload $datafile to store $storename");
    my $tx = $ua->post("http://$storeuser:$storepwd\@$storename:3002/store/put" =>
                            form => { data => { file => $datafile }} );
    $self->app->log->info(" --- End upload $datafile to store $storename");
 
    my $res;
    eval { $res = $tx->result; };
    return $res->body if $res;
    return undef;
}
 
# ---- agent::model db* ---
sub dbExist {
    my ($self, $dbname) = @_;
    return undef unless $dbname;
    my $dblist = $self->dbList;
    foreach my $db (@{$dblist}) {
        return 1 if $db->{"name"} eq $dbname;
    }
    return undef;
}
 
sub dbSize {
    my ($self, $dbname) = @_;
    return undef unless $dbname;
    return -1 unless $self->dbExist($dbname);
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return -1;
    my $query = "select pg_database_size('$dbname');";
    my $sth = $db->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_array;
    $sth->finish;
    $db->disconnect;
    my $dbsize = int($row);
    return $dbsize;
}
 
sub dbList {
    my $self = shift;
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return -1;
    my $query = "select d.datname as name, pg_database_size(d.datname) as size, u.usename as owner 
                    from pg_database d, pg_user u where d.datdba = u.usesysid;";
    my $sth = $db->prepare($query);
    my $rows = $sth->execute  or return -1;
    my @dblist;
    while (my $row = $sth->fetchrow_hashref) {
        push @dblist, $row;
    }
    $sth->finish;
    $db->disconnect;
    return \@dblist;
}
 
sub dbCreate {
    my ($self, $dbname) = @_;
    return undef unless $dbname;
    return 1 if $self->dbExist($dbname);
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return undef;
    my $query = "create database $dbname;";
    my $rows = $db->do($query) or return undef;
    $db->disconnect;
    return 1 if $rows;
    return undef;
}
 
sub dbDrop {
    my ($self, $dbname) = @_;
    return undef unless $self->dbExist($dbname);
    return undef unless $dbname;
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return undef;
    my $query = "drop database $dbname;";
    my $rows = $db->do($query);
    $db->disconnect;
    return 1 if $rows;
    return undef;
}
 
sub dbRename {
    my ($self, $dbname, $newname) = @_;
    return undef unless $self->dbExist($dbname);
    return undef unless $dbname;
    return undef unless $newname;
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return undef;
    my $query = "alter database $dbname rename to $newname;";
    my $rows = $db->do($query);
    $db->disconnect;
    return 1 if $rows;
    return undef;
}
 
sub dbCopy {
    my ($self, $dbname, $newname) = @_;
    return undef unless $dbname;
    return undef unless $newname;
    return undef unless $self->dbExist($dbname);
    return undef if $self->dbExist($newname);
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return undef;
    my $query = "create database $newname template $dbname;";
    my $rows = $db->do($query) or return undef;
    $db->disconnect;
    return 1 if $rows;
    return undef;
}
 
 
sub dbDump {
    my ($self, $dbname) = @_;
 
    return undef unless $dbname;
    return undef unless $self->dbExist($dbname);
 
    my $workdir = "/".$self->app->config("workdir");
 
    do {
        $self->app->log->info("Error: Workdir $workdir not exist");
        return undef; 
    } unless -d $workdir;
    do {
        $self->app->log->info("Error: Cannot write to workdir $workdir");
        return undef; 
    } unless -w $workdir;
 
    my $hostname = $self->app->config("hostname");
    my $timestamp = strftime("%Y%m%d-%H%M%S-%Z", localtime(time));
    my $dbdump = "$workdir/$dbname--$timestamp--$hostname.sqlz";
 
    my $password = $self->password;
    my $pghost = $self->pghost;
    my $username = $self->username;
 
    my $dbsize = $self->dbSize($dbname);
 
    $self->app->log->info("--- Start dump database $dbname with db size $dbsize");
    my $out = qx/PGPASSWORD=$password pg_dump -h $pghost -U $username -Fc -f $dbdump $dbname 2>&1/;
    my $retcode = $?;
 
    my $dumpstat = stat($dbdump);
    my $dumpsize = $dumpstat->size;
    $self->app->log->info("--- End dump database $dbname to $dbdump with size $dumpsize");
 
    return $dbdump if $retcode == 0;
    return undef;
}
 
 
 
#select d.datname as dbname, u.usename as username from pg_database d, pg_user u where d.datdba = u.usesysid order by dbname;
 
sub dbOwner {
    my ($self, $dbname) = @_;
    return undef unless $dbname;
    return undef unless $self->dbExist($dbname);
 
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return undef;
    my $query = "select u.usename as username, d.datname as dbname 
                        from pg_database d, pg_user u 
                        where d.datdba = u.usesysid and d.datname = '$dbname' limit 1;";
    my $sth = $db->prepare($query);
    my $rows = $sth->execute  or return undef;
    my $row = $sth->fetchrow_hashref;
    $db->disconnect;
    return $row if $row;
    return undef;
}
 
sub roleList {
    my ($self) = @_;
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return undef;
    my $query = "select usename as rolename from pg_user;";
    my $sth = $db->prepare($query);
    my $rows = $sth->execute  or return undef;
    my @rolelist;
    while (my $row = $sth->fetchrow_hashref) {
        push @rolelist, $row;
    }
    $sth->finish;
    $db->disconnect;
    return \@rolelist;
}
 
sub roleExist {
    my ($self, $rolename) = @_;
    return undef unless $rolename;
    my $rolelist = $self->roleList;
    foreach my $role (@{$rolelist}) {
        return 1 if $rolename eq $role->{'rolename'};
    }
    return undef;
}
 
 
sub roleCreate {
    my ($self, $rolename, $password) = @_;
    return undef unless $password;
    return undef unless $rolename;
    return undef unless $self->roleExist($rolename);
 
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return undef;
    my $query = "create user '$rolename' encrypted password '$password';";
    my $rows = $db->do($query);
    $db->disconnect;
 
    return 1 if $rows;
    return undef;
}
 
sub roleDrop {
    my ($self, $rolename) = @_;
    return 1;
}
 
sub rolePassword {
    my ($self, $rolename, $password) = @_;
    return undef unless $password;
    return undef unless $rolename;
    return undef unless $self->roleExist($rolename);
 
    my $db = DBI->connect($self->dsn, $self->username, $self->password) or return undef;
    my $query = "alter role $rolename encrypted password '$password';";
    my $rows = $db->do($query);
    $db->disconnect;
 
    return 1 if $rows;
    return undef;
}
 
 
1;
 
#------------------
#--- CONTROLLER ---
#------------------
 
package PGagent::Controller;
 
use utf8;
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Util qw(dumper);
use Mojo::JSON qw(encode_json decode_json);
use Mojo::IOLoop::Subprocess;
use Apache::Htpasswd;
 
sub hello {
    my $self = shift;
    $self->render(json => { message => 'hello' } );
}
 
sub confDump {
    my $self = shift;
    $self->render(json => $self->app->config);
}
 
sub dbList {
    my $self = shift;
    $self->render(json => { dblist => $self->app->model->dbList } );
}
 
sub dbSize {
    my $self = shift;
    my $dbname = $self->req->param('dbname');
    return $self->render(json => { result => "mistake", dbname => $dbname }) unless $dbname;
    my $dbsize = $self->app->model->dbSize($dbname);
    $self->render(json => { name => $dbname, size => $dbsize });
}
 
sub dbCreate {
    my $self = shift;
    my $dbname = $self->req->param('dbname');
    return $self->render(json => { result => "mistake", dbname => $dbname }) unless $dbname;
    my $res = $self->app->model->dbCreate($dbname);
    return $self->render(json => { result => "success", dbname => $dbname }) if $res;
    return $self->render(json => { result => "mistake", dbname => $dbname });
}
 
sub dbDrop {
    my $self = shift;
    my $dbname = $self->req->param('dbname');
    return $self->render(json => { result => "mistake", dbname => $dbname }) unless $dbname;
    my $res = $self->app->model->dbDrop($dbname);
    return $self->render(json => { result => "success", dbname => $dbname }) if $res;
    return $self->render(json => { result => "mistake", dbname => $dbname });
}
 
sub dbRename {
    my $self = shift;
    my $dbname = $self->req->param('dbname');
    my $newname = $self->req->param('newname');
    return $self->render(json => { result => "mistake", dbname => $dbname }) unless $dbname;
    return $self->render(json => { result => "mistake", dbname => $dbname }) unless $newname;
    my $res = $self->app->model->dbRename($dbname, $newname);
    my $dbsize = $self->app->model->dbSize($dbname) if $res;
    return $self->render(json => {result => "success", dbname => $dbname, dbsize => $dbsize}) if $res;
    return $self->render(json => { result => "mistake", dbname => $dbname });
}
 
 
sub dbCopy {
    my $self = shift;
    my $dbname = $self->req->param('dbname');
    my $newname = $self->req->param('newname');
    return $self->render(json => { result => "mistake", dbname => $dbname }) unless $dbname;
    return $self->render(json => { result => "mistake", dbname => $dbname }) unless $newname;
    my $res = $self->app->model->dbCopy($dbname, $newname);
    my $dbsize = $self->app->model->dbSize($dbname) if $res;
    return $self->render(json => {result => "success", dbname => $dbname, dbsize => $dbsize}) if $res;
    return $self->render(json => { result => "mistake", dbname => $dbname });
}
 
sub dbExist {
    my $self = shift;
    my $dbname = $self->req->param('dbname');
    return $self->render(json => { result => "mistake", dbname => '' }) unless $dbname;
    my $res = $self->app->model->dbExist($dbname);
    my $dbsize = $self->app->model->dbSize($dbname) if $res;
    return $self->render(json => { result => "success", dbname => $dbname, dbsize => $dbsize}) if $res;
    return $self->render(json => { result => "mistake", dbname => $dbname });
}
 
sub dbDump {
    my $self = shift;
    my $dbname = $self->req->param('dbname');
 
    my $storename = $self->req->param('store');
    my $storelogin = $self->req->param('storelogin');
    my $storepwd = $self->req->param('storepwd');
 
    my $master = $self->req->param('master');
    my $jobid = $self->req->param('jobid');
    my $magic = $self->req->param('magic');
 
    return $self->render(json => { response => "mistake", dbname => '' }) unless $dbname;
    return $self->render(json => { response => "mistake", dbname => $dbname }) 
            unless $self->app->model->dbExist($dbname);
 
##    my $res = $self->app->model->dbDump($dbname);
 
    my $subprocess = Mojo::IOLoop::Subprocess->new;
    $self->app->log->info(" -- Begin dump database $dbname in subprocess"); 
    $subprocess->run(
        sub {
            my $subprocess = shift;
            my $filename = $self->app->model->dbDump($dbname);
            do { unlink $filename; return undef; } unless $filename;
            my $storeRes = $self->app->model->storePut($filename, $storename, $storelogin, $storepwd);
            do { unlink $filename; return undef; } unless $storeRes;
            unlink $filename;
            return 1;
        },
        sub {
            my ($subprocess, $err, @results) = @_;
            my $pid = $subprocess->pid;
            $self->app->log->info(" -- End dump subprocess $pid for dump database $dbname");
        }
    );
    $subprocess->ioloop->start unless $subprocess->ioloop->is_running;
    $self->render(json => { result => "success", $dbname => $dbname } );
}
 
sub roleList {
    my $self = shift;
    $self->render(json => { result => "success", role => $self->app->model->roleList } );
}
 
sub roleExist {
    my $self = shift;
    my $rolename = $self->req->param('rolename');
    return $self->render(json => { result => "mistake", rolename => undef } )  unless $rolename;
    my $res = $self->app->model->roleExist($rolename);
    return $self->render(json => { result => "success", rolename => $rolename } ) if $res;
    $self->render(json => { result => "mistake", rolename => $rolename } );
}
 
sub rolePassword {
    my $self = shift;
    my $rolename = $self->req->param('rolename');
    return $self->render(json => { result => "mistake", rolename => undef } )  unless $rolename;
    $self->render(json => { result => "success", rolename => $rolename } );
}
 
sub roleCreate {
    my $self = shift;
    my $rolename = $self->req->param('rolename');
    return $self->render(json => { result => "mistake", rolename => undef } )  unless $rolename;
    $self->render(json => { result => "success", rolename => $rolename } );
}
 
sub roleDrop {
    my $self = shift;
    my $rolename = $self->req->param('rolename');
    return $self->render(json => { result => "mistake", rolename => undef } )  unless $rolename;
    $self->render(json => { result => "success", rolename => $rolename } );
}
 
1;
 
#------------
#--- APP ---
#------------
 
package PGagent;
 
use utf8;
use strict;
use warnings;
use Mojo::Base 'Mojolicious';
 
sub startup {
    my $self = shift;
}
 
1;
 
#------------
#--- MAIN ---
#------------
 
use strict;
use warnings;
use utf8;
 
use Mojo::Server::Prefork;
use Mojo::IOLoop::Subprocess;
use Mojo::Util qw(monkey_patch b64_encode b64_decode md5_sum getopt dumper);
use Sys::Hostname qw(hostname);
use File::Basename qw(basename dirname);
use Apache::Htpasswd;
use Cwd qw(getcwd abs_path);
 
my $appfile = abs_path(__FILE__);
my $appdir = dirname($appfile);
my $appname = dirname($appfile).'/'.basename($appfile, ".pl");
$0 = $appname;
 
my $server = Mojo::Server::Prefork->new;
my $app = $server->build_app('PGagent');
 
my $wd = getcwd;
 
$app->config(
        hostname => hostname,
        workdir => "/data/pgagent",
        listenIPv4 => "0.0.0.0",
        listenIPv6 => "[::]",
        listenPort => "3001",
        pghost => "127.0.0.1",
        pguser => "postgres",
        pgpasswd => "password",
        pwdfile => "$appname.pw",
        pidfile => "$appname.pid",
        logfile => "$appname.log",
        conffile => "$appname.conf",
);
 
my $conffile = $app->config('conffile');
do {
    $app->log->debug("Load configuration from $conffile ");
    my $config = $app->plugin( 'JSONConfig', { file => $conffile } );
} if -r $conffile;
 
$app->helper(
    model => sub {
        state $model = PGagent::Model->new(
            $app,
            $app->config("pghost"),
            $app->config("pguser"),
            $app->config("pgpasswd")
        );
    }
);
 
my $r = $app->routes;
 
$r->add_condition(
    auth => sub {
        my ($route, $c, $captures, $hash) = @_;
        my $authStr = $c->req->headers->authorization;
        return undef unless $authStr;
 
        my ($authType, $encAuthPair) = split / /, $authStr;
        return undef unless ($authType eq 'Basic' && $encAuthPair);
 
        my ($username, $password) = split /:/, b64_decode($encAuthPair);
        $c->app->log->debug("Receive auth pair: '$username:$password'");
        return undef unless ($username && $password);
 
        my $passwdFile = $c->app->config('pwdfile');
        my $result = undef;
        eval {
            my $ht = Apache::Htpasswd->new($passwdFile);
            $result = $ht->htCheckPassword($username, $password);
        };
        do { $c->app->log->debug($@); return undef; } if $@;
        return 1 if $result;
        return undef;
    }
);
 
$r->any('/hello')->to('Controller#hello');
$r->any('/db/list')->to('Controller#dbList');
$r->any('/db/create')->to('Controller#dbCreate');
$r->any('/db/drop')->to('Controller#dbDrop');
$r->any('/db/rename')->to('Controller#dbRename');
$r->any('/db/copy')->to('Controller#dbCopy');
$r->any('/db/size')->to('Controller#dbSize');
$r->any('/db/dump')->to('Controller#dbDump');
$r->any('/db/exist')->to('Controller#dbExist');
$r->any('/conf/dump')->to('Controller#confDump');
 
$r->any('/db/owner')->to('Controller#dbOwner');
 
$r->any('/role/list')->to('Controller#roleList');
$r->any('/role/exist')->to('Controller#roleExist');
$r->any('/role/password')->to('Controller#rolePassword');
$r->any('/role/create')->to('Controller#roleCreate');
$r->any('/role/drop')->to('Controller#roleDrop');
 
#$app->mode($app->config("mode"));
$app->secrets([ md5_sum(localtime(time)) ]);
 
$app->hook(before_dispatch => sub {
        my $c = shift;
        my $remoteIP = $c->tx->remote_address;
        $c->app->log->info($remoteIP.' '.$c->req->method.' '.$c->req->url->to_abs->to_string);
});
 
$app->helper('reply.exception' => sub { my $c = shift; return $c->rendered(404); });
$app->helper('reply.not_found' => sub { my $c = shift; return $c->rendered(404); });
 
$server->listen([
    "http://".$app->config("listenIPv4").":".$app->config("listenPort"),
    "http://".$app->config("listenIPv6").":".$app->config("listenPort")
]);
 
$server->heartbeat_interval(3);
$server->heartbeat_timeout(60);
$server->pid_file($app->config('pidfile'));
 
 
my $daemon = 0;
 
if ($daemon) {
    my $pid = fork;
    if ($pid == 0) {
#        setuid($appUID) if $appUID;
#        setgid($appGID) if $appGID;
        $app->log(Mojo::Log->new( path => $app->config('logfile') ));
        open (my $STDOUT2, '>&', STDOUT); open (STDOUT, '>>', '/dev/null');
        open (my $STDERR2, '>&', STDERR); open (STDERR, '>>', '/dev/null');
        chdir($appdir);
        local $SIG{HUP} = sub {
                $app->log->info('Catch HUP signal'); 
                $app->log(Mojo::Log->new(path => $app->config('logfile')));
        };
        $server->run;
    }
} else {
#    setuid($appUID) if $appUID;
#    setgid($appGID) if $appGID;
    $server->run;
}
#EOF

pgmaster.pl

pgmaster.pl
#!/usr/bin/env perl
 
package PGmaster::Model;
 
use strict;
use warnings;
use File::stat;
use DBI;
use Mojo::UserAgent;
use Mojo::JSON qw(encode_json decode_json);
use Mojo::Util qw(md5_sum dumper);
use Socket;
use POSIX;
 
 
sub new {
    my ($class, $app, $dbhost, $dbuser, $dbpasswd, $dbname) = @_;
    my $self = {
        app => $app,
        dbhost => $dbhost,
        dsn => "dbi:Pg:dbname=$dbname;host=$dbhost",
        dbuser => $dbuser,
        dbpasswd => $dbpasswd,
        dbname => $dbname
    };
    bless $self, $class;
    return $self;
}
 
sub dbuser { 
    return shift->{dbuser}; 
}
 
sub dbpasswd { 
    return shift->{dbpasswd}; 
}
 
sub dsn { 
    return shift->{dsn}; 
}
 
sub dbhost { 
    return shift->{dbhost}; 
}
 
sub app { 
    return shift->{app}; 
}
 
sub hostnameAlive {
    my ($self, $hostname) = @_;
    return undef unless gethostbyname($hostname);
    return 1;
}
 
sub parseLabel {
    my ($self, $label) = @_;
    return undef unless $label;
 
    $label =~ s/.sqlz$//;
 
    my ($dbname, $Y, $M, $D, $h, $m, $s, $tz, $host) = 
        $label =~ /([_a-z0-9]{1,64})--([0-9]{4})([0-9]{2})([0-9]{2})-([0-9]{2})([0-9]{2})([0-9]{2})-([A-Z]{2,3})--([_\-a-zA-Z0-9\.]{1,64})/g;
 
    my %data;
    $data{'dbname'} = $dbname;
    $data{'timestamp'} = "$Y-$M-$D $h:$m:$s $tz";
    $data{'source'} = $host;
    return \%data;
}
 
sub timestamp {
    my $self = shift;
    return strftime("%Y-%m-%d %H:%M:%S %Z", localtime(time));
}
 
sub sizeHR {
    my ($self, $size) = @_;
    return $size if $size < 1024;
    return int($size/1024+0.5)."k" if ($size < 1024*1024 && $size > 1024);
    return int($size/(1024*1024)+0.5)."M" if ($size < 1024*1024*1024 && $size > 1024*1024);
    return int($size/(1024*1024*1024)+0.5)."G" if ($size < 1024*1024*1024*1024 && $size > 1024*1024*1024);
}
 
sub sizeWP {
    my ($self, $size) = @_;
    my $out = $size;
    $out =~ s/(\d{9})$/.$1/g if $size > 1000*1000*1000;
    $out =~ s/(\d{6})$/.$1/g if $size > 1000*1000;
    $out =~ s/(\d{3})$/.$1/g if $size > 1000;
    return $out;
}
 
sub sizeR {
    my ($self, $size) = @_;
    return int($size/(1024*1024)+0.5);
}
 
 
#--- model::agent ---
 
sub agentList {
    my ($self, $id)  = @_;
    my $where = '';
    $where = "where id = $id" if $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select * from agent $where order by hostname;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
 
    my @list;
    while (my $row = $sth->fetchrow_hashref) {
        push @list, $row;
    }
    $sth->finish;
    $dbi->disconnect;
    return \@list;
}
 
sub agentExist {
    my ($self, $hostname)  = @_;
    return undef unless $hostname;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select id from agent where hostname = '$hostname' limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    $sth->finish;
    $dbi->disconnect;
    my $id = $row->{'id'};
    return $id if $id;
    return undef;
}
 
sub agentHostname {
    my ($self, $id)  = @_;
    return undef unless $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select hostname from agent where id = $id limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    $sth->finish;
    $dbi->disconnect;
    my $hostname = $row->{'hostname'};
    return $hostname if $hostname;
    return undef;
}
 
sub agentProfile {
    my ($self, $id)  = @_;
    return undef unless $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select * from agent where id = $id limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    $sth->finish;
    $dbi->disconnect;
    return $row if $row;
    return undef;
}
 
sub agentNextID {
    my $self = shift;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select id from agent order by id desc limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    my $id = $row->{'id'};
    $id++;
    $sth->finish;
    $dbi->disconnect;
    return $id;
}
 
sub agentAdd {
    my ($self, $hostname, $username, $password) = @_;
    return undef unless $hostname;
    return undef unless $username;
    return undef unless $password;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd) 
         or return undef;
    my $id = $self->agentNextID;
    my $query = "insert into agent (id, hostname, username, password) 
                    values ($id, '$hostname', '$username', '$password');";
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $id if $rows*1 > 0;
    return undef;
}
 
sub agentDelete {
    my ($self, $id) = @_;
    return undef unless $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "delete from agent where id = $id;";
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $rows*1;
}
 
sub agentUpdate {
    my ($self, $id, $hostname, $username, $password) = @_;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "update agent set
                    hostname = '$hostname',
                    username = '$username',
                    password = '$password'
                    where id = $id;";
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $rows*1;
}
 
sub agentAlive {
    my ($self, $id) = @_;
    return undef unless $id;
    my $hostname = $self->agentHostname($id) or return undef;
    my $ua = Mojo::UserAgent->new(max_redirects => 2) or return undef;
 
    my $tx = $ua->get("http://$hostname:3001/hello");
    my $res;
    eval { $res = decode_json($tx->result->body); };
    return 1 if  $res->{'message'} eq 'hello';
    return undef;
}
 
sub agentDBList {
    my ($self, $id) = @_;
    return undef unless $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select * from db where agentid = $id order by name;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my @list;
    while (my $row = $sth->fetchrow_hashref) {
        push @list, $row;
    }
    $sth->finish;
    $dbi->disconnect;
    return \@list;
}
 
sub agentDBUpdate {
    my ($self, $id) = @_;
    return undef unless $id;
#    my $dblist = $self->agentDBList($id);
    my $hostname = $self->agentHostname($id) or return undef;
    my $ua = Mojo::UserAgent->new(max_redirects => 2) or return undef;
    my $tx = $ua->get("http://$hostname:3001/db/list");
 
 
    my $res;
    eval { $res = decode_json($tx->result->body); };
    return undef if $@;
 
    my $dblist = $res->{'dblist'};
 
    return undef unless $dblist;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "delete from db where agentid = $id;";
    my $rows = $dbi->do($query) or return undef;
    return undef unless $rows;
    foreach my $rec (@{$dblist}) { 
        my $dbname = $rec->{'name'};
        my $dbsize = $rec->{'size'};
        my $dbowner = $rec->{'owner'};
        my $query = "insert into db (agentid, name, size, owner, type) values
                    ($id, '$dbname',  $dbsize, '$dbowner', 'pgsql');";
        my $rows = $dbi->do($query) or return undef;
    }
    $dbi->disconnect;
    return $dblist;
}
 
 
# --- model::store ---
 
sub storeHostname {
    my ($self, $id)  = @_;
    return undef unless $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select hostname from store where id = $id limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    $sth->finish;
    $dbi->disconnect;
    my $hostname = $row->{'hostname'};
    return $hostname if $hostname;
    return undef;
}
 
sub storeProfile {
    my ($self, $id)  = @_;
    return undef unless $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select * from store where id = $id limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    $sth->finish;
    $dbi->disconnect;
    return $row if $row;
    return undef;
}
 
sub storeAlive {
    my ($self, $id) = @_;
    return undef unless $id;
    my $hostname = $self->storeHostname($id) or return undef;
    my $ua = Mojo::UserAgent->new(max_redirects => 2) or return undef;
    my $tx = $ua->get("http://$hostname:3002/hello");
    my $res;
    eval { $res = decode_json($tx->result->body); };
    return 1 if  $res->{'message'} eq 'hello';
    return undef;
}
 
sub storeList {
    my ($self, $id)  = @_;
    my $where = '';
    $where = "where id = '$id'" if $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd);
    my $query = "select * from store $where order by hostname;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
 
    my @list;
    while (my $row = $sth->fetchrow_hashref) {
        push @list, $row;
    }
    $sth->finish;
    $dbi->disconnect;
    return \@list;
}
 
sub storeExist {
    my ($self, $hostname)  = @_;
    return undef unless $hostname;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select id from store where hostname = '$hostname' limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    $sth->finish;
    $dbi->disconnect;
    my $id = $row->{'id'};
    return $id if $id;
    return undef;
}
 
sub storeNextID {
    my $self = shift;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd);
    my $query = "select id from store order by id desc limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    my $id = $row->{'id'};
    $id++;
    $sth->finish;
    $dbi->disconnect;
    return $id;
}
 
sub storeAdd {
    my ($self, $hostname, $username, $password) = @_;
    return undef unless $hostname;
    return undef unless $username;
    return undef unless $password;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $id = $self->storeNextID;
    my $query = "insert into store (id, hostname, username, password) 
                    values ($id, '$hostname', '$username', '$password');";
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $id if $rows*1 > 0;
    return undef;
}
 
sub storeDelete {
    my ($self, $id) = @_;
    return undef unless $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "delete from store where id = $id;";
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $rows*1;
}
 
sub storeUpdate {
    my ($self, $id, $hostname, $username, $password) = @_;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "update store set
                    hostname = '$hostname',
                    username = '$username',
                    password = '$password'
                    where id = $id;";
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $rows*1;
}
 
sub storeDataList {
    my ($self, $id) = @_;
    return undef unless $id;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select * from data where storeid = $id order by name;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my @list;
    while (my $row = $sth->fetchrow_hashref) {
        push @list, $row;
    }
    $sth->finish;
    $dbi->disconnect;
    return \@list;
}
 
sub storeDataUpdate {
    my ($self, $id) = @_;
    return undef unless $id;
    my $hostname = $self->storeHostname($id) or return undef;
 
    my $ua = Mojo::UserAgent->new(max_redirects => 2) or return undef;
    my $tx = $ua->get("http://$hostname:3002/store/list");
 
    my $res = undef;
    eval { $res = decode_json($tx->result->body); };
    return undef if $@;
 
    my $datalist = $res->{'datalist'};
 
    return undef unless $datalist;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "delete from data where storeid = $id;";
    my $rows = $dbi->do($query) or return undef;
    return undef unless $rows;
 
    my @list;
    foreach my $rec (@{$datalist}) { 
        my $dataname = $rec->{'name'};
        my $datasize = $rec->{'size'};
        my $datamtime = $rec->{'mtime'};
 
        # Dataname pattern for dumps dbname--timestamp--sourcehost.ext
        next unless $dataname =~ m/.+--.+--.+\.(sqlz|sql|sql.gz|sql.xz)/;
 
        my $label = $self->app->model->parseLabel($dataname);
        my $dbname = $label->{'dbname'} || 'undef';
        my $source = $label->{'source'} || 'undef';
        my $stamp = $label->{'timestamp'} || '1970-01-01 00:00:00 UTC';
 
        my $query = "insert into data (storeid, name, size, mtime, type, dbname, source, stamp) values
                    ($id, '$dataname',  $datasize, '$datamtime', 'pgsql', '$dbname', '$source', '$stamp');";
        my $rows = $dbi->do($query) or return undef;
        push @list, $rec;
    }
    $dbi->disconnect;
    return \@list;
}
 
sub storeDataFree {
};
 
sub dataList {
    my $self = shift;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select s.hostname as store, d.* from data d, store s where s.id = d.storeid order by d.dbname;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my @list;
    while (my $row = $sth->fetchrow_hashref) {
        push @list, $row;
    }
    $sth->finish;
    $dbi->disconnect;
    return \@list;
}
 
 
sub jobList {
    my $self = shift;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select * from job order by id;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my @list;
    while (my $row = $sth->fetchrow_hashref) {
        push @list, $row;
    }
    $sth->finish;
    $dbi->disconnect;
    return \@list;
}
 
# --- model::job ----
 
sub jobCreate {
    my ($self, @args) = @_;
 
    my $id = $self->jobNextID;
 
    my $begin = strftime("%Y-%m-%d %H:%M:%S %Z", localtime(time));
    my $stop = '1970-01-01 00:00:00 UTC';
    my $type = "undef";
    my $author = "undef";
    my $sourceid = 0;
    my $destid = 0;
    my $status = "undef";
    my $error = "undef";
    my $message = "Job record created";
    my $magic = md5_sum(localtime(time));
 
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd) 
         or return undef;
    my $query = "insert into job (id, begin, stop, author, type, sourceid, destid, status, error, message, magic) 
                    values ($id, '$begin', '$stop', '$author', '$type', $sourceid, $destid, '$status', '$error', '$message', '$magic');";
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $id if $rows*1 > 0;
    return undef;
}
 
 
sub jobList {
    my $self = shift;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select id, 
                    to_char(begin, 'YYYY-MM-DD HH24:MI:SS TZ') as begin,
                    to_char(stop, 'YYYY-MM-DD HH24:MI:SS TZ') as stop,
                    author, type, sourceid, destid, status, error, message, magic from job 
                        order by id desc limit 100;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my @list;
    while (my $row = $sth->fetchrow_hashref) {
        push @list, $row;
    }
    $sth->finish;
    $dbi->disconnect;
    return \@list;
}
 
sub jobNextID {
    my $self = shift;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select id from job order by id desc limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    my $id = $row->{'id'};
    $id++;
    $sth->finish;
    $dbi->disconnect;
    return $id;
}
 
sub jobExist {
    my ($self, $id)  = @_;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select id from job where id = $id limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    $sth->finish;
    $dbi->disconnect;
    $id = $row->{'id'};
    return $id if $id;
    return undef;
}
 
 
sub jobUpdate {
    my ($self, $id, %args) = @_;
 
    return undef unless $id;
    return undef unless $self->jobExist($id);
 
    my $jobProfile = $self->jobProfile($id);
    my $args = \%args;
    my $begin = $args->{'begin'} || $jobProfile->{'begin'};
    my $stop = $args->{'stop'} || $jobProfile->{'stop'};
    my $type = $args->{'type'} || $jobProfile->{'type'};
    my $author = $args->{'author'} || $jobProfile->{'author'};
    my $sourceid = $args->{'sourceid'} || $jobProfile->{'sourceid'};
    my $destid = $args->{'destid'} || $jobProfile->{'destid'};
 
    my $status = $args->{'status'} || $jobProfile->{'status'};
    my $error = $args->{'error'} || $jobProfile->{'error'};
    my $message = $args->{'message'} || $jobProfile->{'message'};
 
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "update job set
                    begin = '$begin',
                    stop = '$stop',
                    author = '$author',
                    type = '$type',
                    sourceid = $sourceid,
                    destid = $destid,
                    status = '$status',
                    error = '$error',
                    message = '$message'
                        where id = $id;";
 
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $id if $rows;
    return undef;
}
 
sub jobDelete {
    my ($self, $id) = @_;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "delete from job where id = $id;";
    my $rows = $dbi->do($query) or return undef;
    $dbi->disconnect;
    return $rows if $rows;
    return undef;
}
 
sub jobProfile {
    my ($self, $id)  = @_;
    my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd)
        or return undef;
    my $query = "select id, to_char(begin, 'YYYY-MM-DD HH24:MI:SS TZ') as begin,
                    to_char(stop, 'YYYY-MM-DD HH24:MI:SS TZ') as stop,
                    author, type, sourceid, destid, status, error, message, magic from job where id = $id limit 1;";
    my $sth = $dbi->prepare($query);
    my $rows = $sth->execute;
    my $row = $sth->fetchrow_hashref;
    $sth->finish;
    $dbi->disconnect;
    return $row if $row;
    return undef;
}
 
 
1;
 
#------------------
#--- CONTROLLER ---
#------------------
 
package PGmaster::Controller;
 
use utf8;
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Util qw(md5_sum dumper);
use Mojo::JSON qw(encode_json decode_json);
 
sub hello {
    my $self = shift;
    $self->render(template => 'hello');
 
#    my $id = $self->app->model->jobCreate;
#    my $idn = $self->app->model->jobUpdate($id, status => 'AAAAAAAAAAAAAAA!!!', type => 'OOOOOOOOOOOO!');
#    my $job = $self->app->model->jobProfile($id);
#
#    my $list = $self->app->model->jobList;
#    $self->render(text => dumper($list));
}
 
 
#--- controller::agent ---
 
sub agentList {
    my $self = shift;
    $self->render(template => 'agentList');
}
 
sub agentFormAdd {
    my $self = shift;
    $self->render(template => 'agentFormAdd');
}
 
sub agentFormUpdate {
    my $self = shift;
    $self->render(template => 'agentFormUpdate', req => $self->req);
}
 
sub agentAdd {
    my $self = shift;
    $self->render(template => 'agentAdd', req => $self->req);
}
 
sub agentUpdate {
    my $self = shift;
    $self->render(template => 'agentUpdate', req => $self->req);
}
 
sub agentDelete {
    my $self = shift;
    $self->render(template => 'agentDelete', req => $self->req);
}
 
sub agentAlive {
    my $self = shift;
    my $id = $self->req->param('id');
    $self->render(template => 'agentAlive', req => $self->req);
}
 
sub agentDBList {
    my $self = shift;
    my $id = $self->req->param('id');
    $self->render(template => 'agentDBList', req => $self->req);
}
 
sub agentDBUpdate {
    my $self = shift;
    $self->render(template => 'agentDBUpdate', req => $self->req);
}
 
# --- controller::store ----
 
sub storeList {
    my $self = shift;
    $self->render(template => 'storeList');
}
 
sub storeFormAdd {
    my $self = shift;
    $self->render(template => 'storeFormAdd');
}
 
sub storeFormUpdate {
    my $self = shift;
    $self->render(template => 'storeFormUpdate', req => $self->req);
}
 
sub storeAdd {
    my $self = shift;
    $self->render(template => 'storeAdd', req => $self->req);
}
 
sub storeUpdate {
    my $self = shift;
    $self->render(template => 'storeUpdate', req => $self->req);
}
 
sub storeDelete {
    my $self = shift;
    $self->render(template => 'storeDelete', req => $self->req);
}
 
sub storeDataList {
    my $self = shift;
    my $id = $self->req->param('id');
    $self->render(template => 'storeDataList', req => $self->req);
}
 
sub storeDataUpdate {
    my $self = shift;
    $self->render(template => 'storeDataUpdate', req => $self->req);
}
 
# ---- controller::db req handlers---
 
sub dbCreate {
    my $self = shift;
    $self->render(template => 'dbCreate', req => $self->req);
}
 
sub dbDrop {
    my $self = shift;
    $self->render(template => 'dbDrop', req => $self->req);
}
 
sub dbRename {
    my $self = shift;
    $self->render(template => 'dbRename', req => $self->req);
}
 
sub dbCopy {
    my $self = shift;
    $self->render(template => 'dbCopy', req => $self->req);
}
 
sub dbDump {
    my $self = shift;
    $self->render(template => 'dbDump', req => $self->req);
}
 
sub dbRestore {
    my $self = shift;
    $self->render(template => 'dbRestore', req => $self->req);
}
 
sub dataList {
    my $self = shift;
    $self->render(template => 'dataList', req => $self->req);
}
 
sub jobList {
    my $self = shift;
    $self->render(template => 'jobList', req => $self->req);
}
 
1;
#-----------
#--- APP ---
#-----------
package PGmaster;
 
use utf8;
use strict;
use warnings;
use Mojo::Base 'Mojolicious';
 
sub startup {
    my $self = shift;
}
 
1;
#------------
#--- MAIN ---
#------------
use strict;
use warnings;
use utf8;
 
use Mojo::Server::Prefork;
use Mojo::IOLoop::Subprocess;
use Mojo::Util qw(monkey_patch b64_encode b64_decode md5_sum getopt dumper);
use File::Basename;
use Sys::Hostname qw(hostname);
 
my $server = Mojo::Server::Prefork->new;
my $appname = basename(__FILE__, ".pl");
my $workdir = dirname(__FILE__);
 
my $app = $server->build_app('PGmaster');
 
$app->config(
    hostname => hostname,
    libdir => "$workdir",
    pidfile => "$appname.pid",
    logfile => "$appname.log",
    mode => "production",
    dbhost => "127.0.0.1",
    dbname => "dumper",
    dbuser => "postgres",
    dbpasswd => "password"
);
 
$app->helper(
    model => sub {
        state $model = PGmaster::Model->new(
            $app,
            $app->config("dbhost"),
            $app->config("dbuser"),
            $app->config("dbpasswd"),
            $app->config("dbname"),
        );
    }
);
 
# --- log all request ---
$app->hook(before_dispatch => sub {
        my $c = shift;
        my $remoteIP = $c->tx->remote_address;
        $c->app->log->debug($remoteIP.' '.$c->req->method.' '.$c->req->url->to_abs->to_string);
});
 
# --- log target request ---
$app->hook(after_render => sub {
        my ($c, $output, $format) = @_;
        my $remote_ip = $c->tx->remote_address;
        my $method = $c->req->method;
 
#        $c->app->log->info($remote_ip.' '.$c->req->method.' '.$c->req->url->to_abs->to_string);
 
        my $base = $c->req->url->base->to_string;
        my $path = $c->req->url->path->to_string;
        $c->app->log->info('Processed '.$remote_ip.' '.$method.' '.$base.''.$path);
});
 
$app->max_request_size(1024*1024*1024);
$app->moniker($appname);
#$app->mode('production');
$app->secrets([ md5_sum(localtime(time)) ]);
#$app->log(Mojo::Log->new(path => $app->config('logfile')));
 
$app->static->paths->[0] = $app->config('libdir').'/public';
$app->renderer->paths->[0] = $app->config('libdir').'/templs';
 
my $r = $app->routes;
 
$r->any('/db/create')->to('Controller#dbCreate');
$r->any('/db/drop')->to('Controller#dbDrop');
$r->any('/db/copy')->to('Controller#dbCopy');
$r->any('/db/rename')->to('Controller#dbRename');
$r->any('/db/dump')->to('Controller#dbDump');
$r->any('/db/restore')->to('Controller#dbRestore');
 
 
$r->any('/hello')->to('Controller#hello');
$r->any('/agent/list')->to('Controller#agentList');
$r->any('/agent/form/add')->to('Controller#agentFormAdd');
$r->any('/agent/form/update')->to('Controller#agentFormUpdate');
$r->any('/agent/add')->to('Controller#agentAdd');
$r->any('/agent/update')->to('Controller#agentUpdate');
$r->any('/agent/delete')->to('Controller#agentDelete');
$r->any('/agent/alive')->to('Controller#agentAlive');
 
$r->any('/agent/dblist')->to('Controller#agentDBList');
$r->any('/agent/dbupdate')->to('Controller#agentDBUpdate');
 
$r->any('/store/list')->to('Controller#storeList');
$r->any('/store/form/add')->to('Controller#storeFormAdd');
$r->any('/store/form/update')->to('Controller#storeFormUpdate');
$r->any('/store/add')->to('Controller#storeAdd');
$r->any('/store/update')->to('Controller#storeUpdate');
$r->any('/store/delete')->to('Controller#storeDelete');
 
$r->any('/store/datalist')->to('Controller#storeDataList');
$r->any('/store/dataupdate')->to('Controller#storeDataUpdate');
 
$r->any('/db/create')->to('Controller#dbCreate');
$r->any('/db/drop')->to('Controller#dbDrop');
$r->any('/db/copy')->to('Controller#dbCopy');
$r->any('/db/rename')->to('Controller#dbRename');
$r->any('/db/dump')->to('Controller#dbDump');
$r->any('/db/restore')->to('Controller#dbRestore');
 
$r->any('/data/list')->to('Controller#dataList');
$r->any('/job/list')->to('Controller#jobList');
 
$server->listen(["http://0.0.0.0:3000"]);
 
$server->pid_file($app->config('pidfile'));
 
$server->heartbeat_interval(3);
$server->heartbeat_timeout(60);
 
$server->run;
#EOF

pgstore.pl

pgstore.pl
#!/usr/bin/env perl
 
#----------------------------------------
#--- BIG CONTROLLER                   ---
#--- OOO, IT IS MEET LAUF CONTROLLER  ---
#----------------------------------------
 
package PGstore::Controller;
 
use utf8;
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Util qw(quote b64_encode b64_decode md5_sum dumper url_escape);
use Mojo::JSON qw(encode_json decode_json);
 
use File::Basename;
use Encode qw(encode decode_utf8 );
 
use Filesys::Df;
use File::stat;
use POSIX;
 
sub renderFile {
        my $self = shift;
        my %args = @_;
 
        utf8::decode($args{filename}) if $args{filename} && !utf8::is_utf8($args{filename});
        utf8::decode($args{filepath}) if $args{filepath} && !utf8::is_utf8($args{filepath});
 
        my $filename = $args{filename};
        my $status = $args{status} || 200;
        my $content_disposition = $args{content_disposition}  || 'attachment';
        my $cleanup = $args{cleanup} // 0;
 
        # Content type based on format
        my $content_type;
        $content_type = $self->app->types->type( $args{format} ) if $args{format};
        $content_type ||= 'application/octet-stream';
 
        # Create asset
        my $asset;
        if ( my $filepath = $args{filepath} ) {
            unless ( -f $filepath && -r $filepath ) {
                $self->app->log->error("Cannot read file [$filepath]. error [$!]");
                return $self->rendered(404);
            }
            $filename ||= fileparse($filepath);
            $asset = Mojo::Asset::File->new( path => $filepath );
            $asset->cleanup($cleanup);
        } elsif ( $args{data} ) {
            $filename ||= $self->req->url->path->parts->[-1] || 'download';
            $asset = Mojo::Asset::Memory->new();
            $asset->add_chunk( $args{data} );
        } else {
            $self->app->log->error('You must provide "data" or "filepath" option');
            return;
        }
        # Set response headers
        my $headers = $self->res->content->headers();
 
        $filename = quote($filename); # quote the filename, per RFC 5987
        $filename = encode("UTF-8", $filename);
 
        $headers->add( 'Content-Type', $content_type . '; name=' . $filename );
        $headers->add( 'Content-Disposition', $content_disposition . '; filename=' . $filename );
 
        # Range, partially based on Mojolicious::Static
        if ( my $range = $self->req->headers->range ) {
            my $start = 0;
            my $size  = $asset->size;
            my $end   = $size - 1 >= 0 ? $size - 1 : 0;
 
            # Check range
            if ( $range =~ m/^bytes=(\d+)-(\d+)?/ && $1 <= $end ) {
                $start = $1;
                $end = $2 if defined $2 && $2 <= $end;
 
                $status = 206;
                $headers->add( 'Content-Length' => $end - $start + 1 );
                $headers->add( 'Content-Range'  => "bytes $start-$end/$size" );
            } else {
                # Not satisfiable
                return $self->rendered(416);
            }
            # Set range for asset
            $asset->start_range($start)->end_range($end);
        } else {
            $headers->add( 'Content-Length' => $asset->size );
        }
        # Stream content directly from file
        $self->res->content->asset($asset);
        return $self->rendered($status);
}
 
sub hello {
    my $self = shift;
    $self->render(json => { message => "hello", result => "success" });
}
 
sub confDump {
    my $self = shift;
    $self->render(json => { config => $self->app->config, result => "success" });
}
 
sub storeList {
    my $self = shift;
    my $datadir = $self->app->config("datadir");
 
    return $self->render(json => { datalist => undef, result => 'mistake' }) unless -d $datadir;
    return $self->render(json => { datalist => undef, result => 'mistake' }) unless -r $datadir;
 
    opendir(my $dh, $datadir);
    my @list;
    while (my $name = readdir($dh)) {
        next if ($name =~ m/^\./);
        my $datafile = "$datadir/$name";
        next if -d $datafile;
        next unless -r $datafile;
        my $st = stat($datafile);
        my $mtime = strftime("%Y-%m-%d %H:%M:%S %Z", localtime($st->mtime));
        push (@list, { name => $name, mtime => $mtime, size => $st->size });
 
    }
    closedir $dh;
    $self->render(json => { datalist => \@list, result => 'success' });
}
 
sub storeGet {
    my $self = shift;
    my $dataname = $self->req->param('dataname');
    my $datadir = $self->app->config("datadir");
    my $file = "/$datadir/$dataname";
    return $self->rendered(404) unless -r $file;
    return $self->rendered(404) if -d $file;
    $self->renderFile(filepath => "$file");
}
 
sub storePut {
    my $self = shift;
    my $datadir = $self->app->config("datadir");
 
    return $self->render(json => { datalist => undef, result => 'mistake', message => "Datadir not exist" }) unless -d $datadir;
    return $self->render(json => { datalist => undef, result => 'mistake', message => "Datadir not writable" }) unless -w $datadir;
 
    return $self->rendered(406) if $self->req->is_limit_exceeded;
 
    my @filenames;
    my $uploads = $self->req->uploads;
 
    foreach my $upload (@{$uploads}) {
        my $dataname = $upload->filename =~ s/[^\w\d\.]+/_/gr;
        my $datasize = $upload->size;
        my $df = df("/$datadir", 1);
        return $self->rendered(406) if $df->{bfree}+1024 < $datasize;
 
        my $datafile = "/$datadir/$dataname";
 
        $upload->move_to($datafile);
 
        # Check real file size
        my $st = stat($datafile);
        my $realsize = $st->size;
        $self->app->log->info("Dataset $dataname size $datasize was uploaded to $datafile with size $realsize");
        do {
            unlink $dataname;
            $self->app->log->info("Error: Dataset $dataname was deleted since datafile size not equal HTTP requested size");
            next;
        } if $datasize != $realsize;
        push @filenames, { name => $dataname, size => $datasize, realsize => $realsize, realname => $datafile };
    }
    $self->render(json => { datalist => \@filenames , result => "success"});
}
 
sub storeFree {
    my $self = shift;
    my $datadir = $self->app->config("datadir");
    return $self->render(json => { total => 0, free => 0, result => 'mistake', message => 'Datadir not exist' }) unless -d $datadir;
    return $self->render(json => { total => 0, free => 0, result => 'mistake', message => 'Connot read datadir' }) unless -r $datadir;
    my $df = df("/$datadir", 1);
    $self->render(json => { total => $df->{blocks}, free => $df->{bfree}, result => 'success' });
}
 
sub storeDelete {
    my $self = shift;
    my $datadir = "/".$self->app->config("datadir");
    my $dataname = $self->req->param('dataname');
    return $self->render(json => { dataname => $dataname, result => 'mistake' }) unless $dataname;
    return $self->render(json => { dataname => $dataname, result => 'mistake', message => 'Datadir not exist' }) unless -d $datadir;
    return $self->render(json => { dataname => $dataname, result => 'mistake', message => 'Cannot read datadir' }) unless -w $datadir;
 
    my $datafile = "$datadir/$dataname";
    return $self->render(json => { dataname => $dataname, result => 'success', size => -1, message => 'The dataset not exist' }) unless -f $datafile;
 
    my $st = stat($datafile);
    my $datasize = $st->size;
 
    return $self->render(json => { dataname => $dataname, result => 'success', size => $datasize }) if unlink($datafile);
    $self->render(json => { dataname => $dataname, result => 'mistake', size => $datasize });
}
 
sub storeLink {
    my $self = shift;
    $self->render(json => { result => 'success' });
}
 
sub storeRename {
    my $self = shift;
    $self->render(json => { result => 'success' });
}
 
sub storeMigrate {
    my $self = shift;
    $self->render(json => { result => 'success' });
}
 
 
1;
 
#-----------
#--- APP ---
#-----------
 
package PGstore;
 
use utf8;
use strict;
use warnings;
use Mojo::Base 'Mojolicious';
 
sub startup {
    my $self = shift;
}
 
1;
 
#------------
#--- MAIN ---
#------------
 
use strict;
use warnings;
use utf8;
 
use Mojo::Server::Prefork;
use Mojo::IOLoop::Subprocess;
use Mojo::Util qw(monkey_patch b64_encode b64_decode md5_sum getopt dumper);
use Sys::Hostname qw(hostname);
use File::Basename qw(basename dirname);
use Apache::Htpasswd;
use Cwd qw(getcwd abs_path);
use EV;
 
my $appfile = abs_path(__FILE__);
my $appdir = dirname($appfile);
my $appname = dirname($appfile).'/'.basename($appfile, ".pl");
$0 = $appname;
 
my $server = Mojo::Server::Prefork->new;
my $app = $server->build_app('PGstore');
 
my $wd = getcwd;
 
$app->config(
        hostname => hostname,
        datadir => "/data/pgstore",
        listenIPv4 => "0.0.0.0",
        listenIPv6 => "[::]",
        listenPort => "3002",
        pghost => "127.0.0.1",
        pguser => "postgres",
        pgpasswd => "password",
        pwdfile => "$appname.pw",
        pidfile => "$appname.pid",
        logfile => "$appname.log",
        conffile => "$appname.conf",
        maxrequestsize => 1024*1024*1024,
);
 
$ENV{MOJO_TMPDIR} = $app->config("datadir");
 
$app->max_request_size($app->config("maxrequestsize"));
$app->moniker($appname);
$app->secrets([ md5_sum(localtime(time)) ]);
 
 
my $conffile = $app->config('conffile');
do {
    $app->log->debug("Load configuration from $conffile ");
    my $config = $app->plugin( 'JSONConfig', { file => $conffile } );
} if -r $conffile;
 
#$app->helper(
#    model => sub {
#        state $model = PGstore::Model->new($app);
#    }
#);
 
my $r = $app->routes;
 
$r->any('/hello')->to('Controller#hello');
$r->any('/store/list')->to('Controller#storeList');
$r->any('/store/get')->to('Controller#storeGet');
$r->any('/store/put')->to('Controller#storePut');
$r->any('/store/free')->to('Controller#storeFree');
$r->any('/store/delete')->to('Controller#storeDelete');
$r->any('/conf/dump')->to('Controller#confDump');
 
$r->any('/store/link')->to('Controller#storeLink');
$r->any('/store/rename')->to('Controller#storeRename');
$r->any('/store/migrate')->to('Controller#storeMigrate');
 
 
$r->add_condition(
    auth => sub {
        my ($route, $c, $captures, $hash) = @_;
        my $authStr = $c->req->headers->authorization;
        return undef unless $authStr;
 
        my ($authType, $encAuthPair) = split / /, $authStr;
        return undef unless ($authType eq 'Basic' && $encAuthPair);
 
        my ($username, $password) = split /:/, b64_decode($encAuthPair);
        $c->app->log->debug("Receive auth pair: '$username:$password'");
        return undef unless ($username && $password);
 
        my $passwdFile = $c->app->config('pwdfile');
        my $result = undef;
        eval {
            my $ht = Apache::Htpasswd->new($passwdFile);
            $result = $ht->htCheckPassword($username, $password);
        };
        do { $c->app->log->debug($@); return undef; } if $@;
        return 1 if $result;
        return undef;
    }
);
 
 
#$app->mode($app->config("mode"));
$app->secrets([ md5_sum(localtime(time)) ]);
 
$app->hook(before_dispatch => sub {
        my $c = shift;
        my $remoteIP = $c->tx->remote_address;
        $c->app->log->info($remoteIP.' '.$c->req->method.' '.$c->req->url->to_abs->to_string);
});
 
$app->helper('reply.exception' => sub { my $c = shift; return $c->rendered(404); });
$app->helper('reply.not_found' => sub { my $c = shift; return $c->rendered(404); });
 
$server->listen([
    "http://".$app->config("listenIPv4").":".$app->config("listenPort"),
    "http://".$app->config("listenIPv6").":".$app->config("listenPort")
]);
 
$server->heartbeat_interval(3);
$server->heartbeat_timeout(60);
$server->pid_file($app->config('pidfile'));
 
 
my $daemon = 0;
 
if ($daemon) {
    my $pid = fork;
    if ($pid == 0) {
#        setuid($appUID) if $appUID;
#        setgid($appGID) if $appGID;
        $app->log(Mojo::Log->new( path => $app->config('logfile') ));
        open (my $STDOUT2, '>&', STDOUT); open (STDOUT, '>>', '/dev/null');
        open (my $STDERR2, '>&', STDERR); open (STDERR, '>>', '/dev/null');
        chdir($appdir);
        local $SIG{HUP} = sub {
                $app->log->info('Catch HUP signal'); 
                $app->log(Mojo::Log->new(path => $app->config('logfile')));
        };
        $server->run;
    }
} else {
#    setuid($appUID) if $appUID;
#    setgid($appGID) if $appGID;
    $server->run;
}
#EOF

agentAdd.html.ep

agentAdd.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% my $hostname = $req->param('hostname');
% my $username = $req->param('username');
% my $password = $req->param('password');
 
% my $agentID = $self->app->model->agentExist($hostname);
% do {
    <div class="callout alert">
        Agent <%= $hostname %> yet exist.
    </div>
% } if $agentID;
 
% do {
%   my $agentID = $self->app->model->agentAdd($hostname, $username, $password);
%   do {
      <div class="callout success">
        Agent <%= $hostname %> was added successful with id <%= $agentID %>.
      </div>
 
    <div class="text-center">
        <a class="button" href="/agent/list">Agent List</a>
        <a class="button hollow" href="/agent/dblist?id=<%= $agentID %>">DBList</a>
        <a class="button" href="/agent/form/add">Add yet</a>
    </div>
 
%   } if $agentID;
 
%   do {
        <div class="callout alert">
          Agent <%= $hostname %> was added unsuccessful.
        </div>
        <div class="text-center">
            <a class="button" href="/agent/list">Agent List</a>
            <a class="button" href="/agent/form/add">Add yet</a>
        </div>
%   } unless $agentID;
% } unless $agentID;
 
%#EOF

agentDBList.html.ep

agentDBList.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% my $model = $self->app->model;
 
% my $agentID = $req->param('id');
% my $agentHostname = $model->agentHostname($agentID);
 
% my $agentAlive = $model->agentAlive($agentID);
% unless ($agentAlive) {
    <div class="callout alert">
        Oops...Agent <%= $agentHostname %> not responded.
        Use cached database list.
    </div>
% }
 
% if ($agentAlive) {
%    $model->agentDBUpdate($agentID);
% }
 
% my $dbList = $self->app->model->agentDBList($agentID);
% my $storeList = $self->app->model->storeList;
 
% my $num = 1;
% foreach my $db (@{$dbList}) {
%   my $dbname = $db->{'name'};
%   my $dbsize = $db->{'size'};
%   my $dbowner = $db->{'owner'};
 
%#  --- dump database ---
    <div class="reveal" id="modal-dump-<%= $dbname %>" data-reveal>
        <div class="text-center">
            <h5>Dump database <%= $dbname %> on <%= $agentHostname %></h5>
        </div>
        <form accept-charset="UTF-8" action="/db/dump" method="get" data-abide novalidate>
            <input type="hidden" name="agentid" value="<%= $agentID %>" />
            <input type="hidden" name="dbname" value="<%= $dbname %>" />
 
            <label>Store hostname
                <select name="storeid" id="store-hostname" required>
                    <option value=""></option>
%                   foreach my $store (@{$storeList}) {
%                      my $storeName = $store->{'hostname'};
%                      my $storeID = $store->{'id'};
                       <option value="<%= $storeID %>"><%= $storeName %></option>
%                   }
                </select>
            </label>
            <p class="text-center">
                <button type="submit" class="button success">Accept</button>
                <button class="button" data-close="modal-dump-<%= $dbname %>" type="button">Escape</button>
            </p>
        </form>
        <button class="close-button" data-close="modal-dump-<%= $dbname %>" type="button">&times;</button>
    </div>
 
%#  --- drop database ---
    <div class="reveal" id="modal-drop-<%= $dbname %>" data-reveal>
        <div class="text-center">
            <h5>Drop database <%= $dbname %> on <%= $agentHostname %> </h5>
        </div>
        <form accept-charset="UTF-8" action="/db/drop" method="get" data-abide novalidate>
            <input type="hidden" name="agentid" value="<%= $agentID %>" />
            <input type="hidden" name="dbname" value="<%= $dbname %>" />
            <p class="text-center">
                <button type="submit" class="button alert">Accept</button>
                <button class="button" data-close="modal-drop-<%= $dbname %>" type="button">Escape</button>
            </p>
        </form>
        <button class="close-button" data-close="modal-drop-<%= $dbname %>" type="button">&times;</button>
    </div>
 
%#  --- rename database ---
    <div class="reveal" id="modal-rename-<%= $dbname %>" data-reveal>
        <div class="text-center">
            <h5>Rename database <%= $dbname %> on <%= $agentHostname %> </h5>
        </div>
        <form accept-charset="UTF-8" action="/db/rename" method="get" data-abide novalidate>
            <input type="hidden" name="agentid" value="<%= $agentID %>" />
            <input type="hidden" name="dbname" value="<%= $dbname %>" />
            <label>New name
                <input type="text" name="newname" value="<%= $dbname %>" placeholder="new db name" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{3,64}"/>
                <span class="form-error">mandatory, 3 or more letter</span>
            </label>
            <p class="text-center">
                <button type="submit" class="button success">Accept</button>
                <button class="button" data-close="modal-rename-<%= $dbname %>" type="button">Escape</button>
            </p>
        </form>
        <button class="close-button" data-close="modal-rename-<%= $dbname %>" type="button">&times;</button>
    </div>
 
%#  --- copy database ---
    <div class="reveal" id="modal-copy-<%= $dbname %>" data-reveal>
        <div class="text-center">
            <h5>Copy database <%= $dbname %> on <%= $agentHostname %> </h5>
        </div>
        <form accept-charset="UTF-8" action="/db/copy" method="get" data-abide novalidate>
            <input type="hidden" name="agentid" value="<%= $agentID %>" />
            <input type="hidden" name="dbname" value="<%= $dbname %>" />
            <label>Copy name
                <input type="text" name="newname" value="<%= $dbname %>_copy" placeholder="db name of copy" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{3,64}"/>
                <span class="form-error">mandatory, 3 or more letter</span>
            </label>
            <p class="text-center">
                <button type="submit" class="button success">Accept</button>
                <button class="button" data-close="modal-copy-<%= $dbname %>" type="button">Escape</button>
            </p>
        </form>
        <button class="close-button" data-close="modal-copy-<%= $dbname %>" type="button">&times;</button>
    </div>
 
%#  --- operation modal ---
    <div class="reveal" id="modal-<%= $dbname %>" data-reveal>
        <div class="text-center">
            <h5><i class="fi-database" style="font-size:1.3em;"></i>&nbsp;Database <%= $dbname %> on <%= $agentHostname %> </h5>
            <div class="stacked-for-small button-group">
                <a class="button" href="#" data-open="modal-dump-<%= $dbname %>"><i class="fi-archive" style="font-size:1.3em;"></i>&nbsp;Dump</a>
                <a class="button" href="#" data-open="modal-rename-<%= $dbname %>"><i class="fi-pencil" style="font-size:1.3em;"></i>&nbsp;Rename</a>
                <a class="button" href="#" data-open="modal-copy-<%= $dbname %>"><i class="fi-page-copy" style="font-size:1.3em;"></i>&nbsp;Copy</a>
                <a class="button alert" href="#" data-open="modal-drop-<%= $dbname %>"><i class="fi-trash" style="font-size:1.3em;"></i>&nbsp;Drop</a>
            </div>
        </div>
        <button class="close-button" data-close="modal-<%= $dbname %>" type="button">&times;</button>
    </div>
%   $num++;
% }
 
<div class="text-center">
    <h5>
        Agent <%= $agentHostname%> database list&nbsp;
        <a href="/agent/dblist?id=<%= $agentID %>"><i class="fi-refresh" style="font-size:1.3rem;"></i></a>
    </h5>
</div>
 
<table id="table" class="display" >
    <thead>
      <tr>
        <td>#</td>
        <td>dbname</td>
        <td>size</td>
        <td>owner</td>
      </tr>
    </thead>
    <tbody>
% $num = 1;
% foreach my $db (@{$dbList}) {
%   my $dbname = $db->{'name'};
%   my $dbsize = $db->{'size'};
%   my $dbowner = $db->{'owner'};
    <tr>
        <td><%= $num %></td>
        <td><a href="#" data-open="modal-<%= $dbname %>"><%= $dbname %></a></td>
        <td><%= $dbsize %></td>
        <td><%= $dbowner %></td>
    </tr>
%   $num++;
% }
    </tbody>
</table>
 
<script>
$.extend(true, $.fn.dataTable.defaults, {
    "searching": false,
    "ordering": true
} );
 
$(document).ready(function() {
    $('#table').DataTable();
});
</script>
 
%#EOF

agentDBUpdate.html.ep

agentDBUpdate.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% my $id = $req->param('id');
 
% my $model = $self->app->model;
 
% my $agentHostname = $model->agentHostname($id);
% my $res = $model->hostnameAlive($agentHostname);
% my $dbList;
% do { $dbList = $model->agentDBUpdate($id); } if $res;
 
% do {
    <div class="callout alert">
        Oops...Agent hostname <%= $agentHostname %> not resolved.
    </div>
% } unless $dbList;
 
 
% do {
    <div class="callout success">
        Agent <%= $agentHostname %> DB list updated
    </div>
% } if $dbList;
 
<div class="text-center">
    <a class="button hollow" href="/agent/dblist?id=<%= $id %>">DBList</a>
    <a class="button hollow" href="/agent/dbupdate?id=<%= $id %>">DBLUpdate</a>
</div>
 
% do {
 
    <table id="table" class="display" >
        <thead>
          <tr>
            <td>#</td>
            <td>name</td>
            <td>size</td>
          </tr>
        </thead>
        <tbody>
%     my $num = 1;
%     foreach my $db (@{$dbList}) {
%       my $name = $db->{'name'};
%       my $size = $db->{'size'};
%       my $owner = $db->{'owner'};
        <tr>
            <td><%= $num %></td>
            <td><%= $name %></td>
            <td><%= $size %></td>
        </tr>
%       $num++;
%     }
        </tbody>
    </table>
% } if $dbList;
%#EOF

agentDelete.html.ep

agentDelete.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
% my $id = $req->param('id');
% my $res = $self->app->model->agentDelete($id);
 
% do {
    <div class="callout success">
        Agent id <%= $id %> was deleted successful.
    </div>
% } if $res;
 
% do {
    <div class="callout alert">
        Agent id <%= $id %> was deleted unsuccessful.
    </div>
% } unless $res;
 
<div class="text-center">
    <a class="button hollow" href="/agent/list">Agent List</a>
</div>
 
 
%#EOF

agentFormAdd.html.ep

agentFormAdd.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
<div class="row columns">
<form accept-charset="UTF-8" id="user-create-form" action="/agent/add" method="post" data-abide novalidate>
    <h5>Add DB agent</h5>
 
    <label>hostname
        <input type="text" name="hostname" placeholder="agent hostname" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{3,64}"/>
        <span class="form-error">mandatory, 3 or more letter</span>
    </label>
 
    <label>username
        <input type="text" name="username" placeholder="login" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
        <span class="form-error">mandatory, 5 or more letter</span>
    </label>
 
    <label>password
        <input type="text" name="password" placeholder="password" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
        <span class="form-error">mandatory, 5 or more letter</span>
    </label>
    <p class="text-center">
        <button type="submit" class="button">Accept</button>
    </p>
</form>
</div>
 
%#EOF

agentFormUpdate.html.ep

agentFormUpdate.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
% my $id = $req->param('id');
% my $agent = pop @{$self->app->model->agentList($id)};
 
% my $hostname = $agent->{'hostname'};
% my $username = $agent->{'username'};
% my $password = $agent->{'password'};
 
<form accept-charset="UTF-8" action="/agent/update" method="post" data-abide novalidate>
 
        <h5>Update DB agent <%= $agent->{"hostname"} %></h5>
        <input type="hidden" name="id" value="<%= $id %>"/>
 
        <label>hostname
            <input type="text" name="hostname" placeholder="hostname" value="<%= $hostname %>" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
            <span class="form-error">mandatory parameter, 5 or more letter</span>
        </label>
 
        <label>username
            <input type="text" name="username" placeholder="username" value="<%= $username %>" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
            <span class="form-error">mandatory, 5 or more letter</span>
        </label>
 
        <label>password
            <input type="password" name="password" placeholder="password" value="<%= $password %>" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
            <span class="form-error">mandatory, 5 or more letter</span>
        </label>
 
        <p class="text-center">
            <button type="submit" class="button">Accept</button>
        </p>
</form>
 
%#EOF

agentList.html.ep

agentList.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(b64_encode b64_decode md5_sum dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
% my $agentList = $self->app->model->agentList;
 
% foreach my $agent (@{$agentList}) {
%   my $agentID = $agent->{'id'};
%   my $hostname = $agent->{'hostname'};
 
    <div class="reveal" id="modal-delete-<%= $agentID %>" data-reveal>
        <div class="text-center">
            <h5>Do you want to delete agent <%= $hostname %>?</h5>
            <a class="button hollow" data-close aria-label="Close modal">Escape</a>
            <a class="button hollow alert" href="/agent/delete?id=<%= $agentID %>">Delete</a>
        </div>
        <button class="close-button" data-close aria-label="Close modal" type="button">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
 
    <div class="reveal" id="modal-create-<%= $agentID %>" data-reveal>
        <h5>Create new database on agent [<%= $hostname %>] </h5>
        <form accept-charset="UTF-8" action="/db/create" method="get" data-abide novalidate>
            <input type="hidden" name="agentid" value="<%= $agentID %>" />
            <label>New name
                <input type="text" name="dbname" value="new_empty_db" placeholder="empty_db" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{3,64}"/>
                <span class="form-error">mandatory, 3 or more letter</span>
            </label>
            <p class="text-center">
                <button type="submit" class="button">Accept</button>
            </p>
        </form>
        <button class="close-button" data-close="modal-create-<%= $agentID %>" type="button">&times;</button>
    </div>
 
    <div class="reveal" id="modal-<%= $agentID %>" data-reveal>
        <div class="text-center">
            <h5>Agent <%= $hostname %></h5>
            <div class="stacked-for-small button-group">
                <a class="button" href="/agent/form/update?id=<%= $agentID %>">Reconf</a>
                <a class="button" href="/agent/dblist?id=<%= $agentID %>">DBList</a>
                <a class="button" href="#" data-open="modal-create-<%= $agentID %>">DBCreate</a>
                <a class="button" href="/agent/dbupdate?id=<%= $agentID %>">DBUpdate</a>
                <a class="button alert" href="#" data-open="modal-delete-<%= $agentID %>">Delete</a>
            </div>
        </div>
        <button class="close-button" data-close aria-label="Close modal" type="button">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
 
 
% }
 
<div class="text-center">
    <h5>Agent list&nbsp;<a href="/agent/list"><i class="fi-refresh" style="font-size:1.3rem;"></i></a></h5>
</div>
 
<table id="table" class="display" >
    <thead>
      <tr>
        <td>#</td>
        <td>hostname</td>
      </tr>
    </thead>
    <tbody>
% my $num = 1;
% foreach my $agent (@{$agentList}) {
%   my $agentID = $agent->{'id'};
%   my $hostname = $agent->{'hostname'};
    <tr>
        <td><%= $num %></td>
        <td><a href="#" data-open="modal-<%= $agentID %>"><%= $hostname %></a></td>
    </tr>
%   $num++;
% }
    </tbody>
</table>
 
%#EOF

agentUpdate.html.ep

agentUpdate.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
% my $agentID = $req->param('id');
% my $hostname = $req->param('hostname');
% my $username = $req->param('username');
% my $password = $req->param('password');
 
% my $res = $self->app->model->agentUpdate($agentID, $hostname, $username, $password);
 
% do {
    <div class="callout success">
        Agent <%= $hostname %> was updated successful.
    </div>
% } if $res;
 
% do {
    <div class="callout alert">
        Agent <%= $hostname %> was updated unsuccessful.
    </div>
% } unless $res;
 
<div class="text-center">
    <a class="button hollow" href="/agent/dblist?id=<%= $agentID %>">DBList</a>
</div>
 
 
%#EOF

dataList.html.ep

dataList.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% my $dataList = $self->app->model->dataList;
 
<div class="text-center">
    <h5>Generic dataset list</h5>
</div>
 
<table id="table" class="display" >
    <thead>
      <tr>
        <td>#</td>
        <td>store</td>
        <td>dbdump</td>
        <td>date</td>
        <td>size</td>
        <td>source</td>
      </tr>
    </thead>
    <tbody>
% my $num = 1;
% foreach my $data (@{$dataList}) {
%   my $filename = $data->{'name'};
%   my $dbname = $data->{'dbname'};
%   my $stamp = $data->{'stamp'};
%   my $size = $data->{'size'};
%   my $store = $data->{'store'};
%   my $storeID = $data->{'storeid'};
%   my $source = $data->{'source'};
    <tr>
        <td><%= $num %></td>
        <td><a href="/store/datalist?id=<%= $storeID %>"><%= $store %></a></td>
        <td><%= $dbname %></td>
        <td><%= $stamp %></td>
        <td><%= $size %></td>
        <td><%= $source %></td>
    </tr>
%   $num++;
% }
    </tbody>
</table>
 
<script>
$.extend(true, $.fn.dataTable.defaults, {
    "searching": true,
    "ordering": true,
    "lengthChange": false,
});
 
$(document).ready(function() {
    $('#table').DataTable({
        "info": false,
        "processing": true
 
    });
});
</script>
 
 
%#EOF

dbCopy.html.ep

dbCopy.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Mojo::JSON qw(j encode_json decode_json);
 
% use utf8;
% use strict;
 
% my $model = $self->app->model;
 
% my $dbname = $req->param('dbname') || undef;
% my $agentID = $req->param('agentid') || undef;
% my $newname = $req->param('newname') || undef;
 
% my $agentProfile = $model->agentProfile($agentID);
% my $agentHostname = $agentProfile->{'hostname'} || '';
% my $agentUsername = $agentProfile->{'username'} || '';
% my $agentPassword = $agentProfile->{'password'} || '';
 
% my $jobID = $model->jobCreate;
% $model->jobUpdate($jobID, type => 'dbcopy', sourceid => $agentID, destid => $agentID);
% my $jobMagic = $model->jobProfile($jobID)->{'magic'};
 
% my $ua = Mojo::UserAgent->new(max_redirects => 5);
 
% my $param = "dbname=$dbname";
% $param = $param."&newname=$newname";
 
% my $tx = $ua->get("http://$agentHostname:3001/db/copy?$param");
 
% my $body = '{"result":"mistake"}';
% eval { $body = $tx->result->body };
% my $res = decode_json($body);
 
% do {
%   $model->jobUpdate($jobID, status => 'done', error => 'noerr', stop => $model->timestamp);
    <div class="callout success">
        Agent <%= $agentHostname %> copy database <%= $dbname %> to <%= $newname %> as job <%= $jobID %>.
    </div>
%   $model->agentDBUpdate($agentID);
% } if $res->{'result'} eq 'success';
 
% do {
%   $model->jobUpdate($jobID, status => 'pushjob', error => 'pusherr', stop => $model->timestamp);
    <div class="callout alert">
        Agent <%= $agentHostname %> cannot copy <%= $dbname %>. 
        Job <%= $jobID %> was cancel.
    </div>
% } if $res->{'result'} ne 'success';
 
<div class="text-center">
    <a class="button hollow" href="/agent/dblist?id=<%= $agentID %>">DBList</a>
</div>
 
%#EOF

dbCreate.html.ep

dbCreate.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
%#EOF

dbDrop.html.ep

dbDrop.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Mojo::JSON qw(j encode_json decode_json);
 
% use utf8;
% use strict;
 
% my $model = $self->app->model;
 
% my $dbname = $req->param('dbname') || undef;
% my $agentID = $req->param('agentid') || undef;
 
% my $agentProfile = $model->agentProfile($agentID);
% my $agentHostname = $agentProfile->{'hostname'} || '';
% my $agentUsername = $agentProfile->{'username'} || '';
% my $agentPassword = $agentProfile->{'password'} || '';
 
% my $jobID = $model->jobCreate;
% $model->jobUpdate($jobID, type => 'dbdrop', sourceid => $agentID, destid => $agentID);
% my $jobMagic = $model->jobProfile($jobID)->{'magic'};
 
% my $ua = Mojo::UserAgent->new(max_redirects => 5);
 
% my $param = "dbname=$dbname";
 
% my $tx = $ua->get("http://$agentHostname:3001/db/drop?$param");
 
% my $body = '{"result":"mistake"}';
% eval { $body = $tx->result->body };
% my $res = decode_json($body);
 
% do {
%   $model->jobUpdate($jobID, status => 'done', error => 'noerr', stop => $model->timestamp);
    <div class="callout success">
        Agent <%= $agentHostname %> drop database <%= $dbname %> as job <%= $jobID %>.
    </div>
%   $model->agentDBUpdate($agentID);
% } if $res->{'result'} eq 'success';
 
 
 
% do {
%   $model->jobUpdate($jobID, status => 'pushjob', error => 'pusherr', stop => $model->timestamp);
    <div class="callout alert">
        Agent <%= $agentHostname %> cannot drop <%= $dbname %>. 
        Job <%= $jobID %> was cancel.
    </div>
% } if $res->{'result'} ne 'success';
 
<div class="text-center">
    <a class="button hollow" href="/agent/dblist?id=<%= $agentID %>">DBList</a>
</div>
 
 
%#EOF

dbDump.html.ep

dbDump.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Mojo::JSON qw(j encode_json decode_json);
 
% use utf8;
% use strict;
 
% my $model = $self->app->model;
 
% my $dbname = $req->param('dbname') || undef;
% my $agentID = $req->param('agentid') || undef;
% my $storeID = $req->param('storeid') || undef;
 
% my $agentProfile = $model->agentProfile($agentID);
% my $agentHostname = $agentProfile->{'hostname'} || '';
% my $agentUsername = $agentProfile->{'username'} || '';
% my $agentPassword = $agentProfile->{'password'} || '';
 
% my $storeProfile = $model->storeProfile($storeID);
% my $storeHostname = $storeProfile->{'hostname'} || '';
% my $storeUsername = $storeProfile->{'username'} || '';
% my $storePassword = $storeProfile->{'password'} || '';
 
% my $masterHostname = $self->app->config('hostname');
% my $jobID = $model->jobCreate;
% $model->jobUpdate($jobID, type => 'dbdump', sourceid => $agentID, destid => $storeID);
% my $jobMagic = $model->jobProfile($jobID)->{'magic'};
 
% my $agentAlive = $model->agentAlive($agentID);
% unless ($agentAlive) {
    <div class="callout alert">
        Oops...Agent <%= $agentHostname %> not respond.
        Please, check agent configuration.
    </div>
%   $model->jobUpdate($jobID, error => 'connecterr', stop => $model->timestamp);
% }
 
% my $storeAlive = $model->storeAlive($storeID);
% unless ($storeAlive) {
    <div class="callout alert">
        Oops.. Store <%= $storeHostname %> not respond.
        Please, check store configuration.
    </div>
%   $model->jobUpdate($jobID, error => 'connecterr', stop => $model->timestamp);
% }
 
% if ($agentAlive && $storeAlive) {
%     my $ua = Mojo::UserAgent->new(max_redirects => 5);
 
%     my $param = "dbname=$dbname";
%     $param = $param."&store=$storeHostname";
%     $param = $param."&storelogin=$storeUsername";
%     $param = $param."&storepwd=$storePassword";
 
%     $param = $param."&master=$masterHostname";
%     $param = $param."&jobid=$jobID";
%     $param = $param."&magic=$jobMagic";
 
%     $model->jobUpdate($jobID, status => 'pushjob');
%     my $tx = $ua->get("http://$agentUsername:$agentPassword\@$storeHostname:3001/db/dump?$param");
 
%     my $body = '{"result":"mistake"}';
%     eval { $body = $tx->result->body };
%     my $res = decode_json($body);
 
%     if ( $res->{'result'} eq 'success' ) {
        <div class="callout success">
            Agent <%= $agentHostname %> now create dump of database <%= $dbname %> as job <%= $jobID %>.
        </div>
%     }
 
%     if ( $res->{'result'} ne 'success' ) {
        <div class="callout alert">
            Agent <%= $agentHostname %> not correct responded for the request.
            Job <%= $jobID %> was cancel.
        </div>
%       $model->jobUpdate($jobID, error => 'pushjoberr', stop => $model->timestamp);
%     }
% }
 
<div class="text-center">
    <a class="button hollow" href="/agent/dblist?id=<%= $agentID %>">DBList</a>
    <a class="button hollow" href="/store/datalist?id=<%= $storeID %>">DataList</a>
</div>
%#EOF

dbRename.html.ep

dbRename.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Mojo::JSON qw(j encode_json decode_json);
 
% use utf8;
% use strict;
 
% my $model = $self->app->model;
 
% my $dbname = $req->param('dbname') || undef;
% my $agentID = $req->param('agentid') || undef;
% my $newname = $req->param('newname') || undef;
 
% my $agentProfile = $model->agentProfile($agentID);
% my $agentHostname = $agentProfile->{'hostname'} || '';
% my $agentUsername = $agentProfile->{'username'} || '';
% my $agentPassword = $agentProfile->{'password'} || '';
 
<div class="text-center">
    <a class="button hollow" href="/agent/dblist?id=<%= $agentID %>">DBList</a>
</div>
 
% my $jobID = $model->jobCreate;
% $model->jobUpdate($jobID, type => 'dbrename', sourceid => $agentID, destid => $agentID);
% my $jobMagic = $model->jobProfile($jobID)->{'magic'};
 
% my $ua = Mojo::UserAgent->new(max_redirects => 5);
 
% my $param = "dbname=$dbname";
% $param = $param."&newname=$newname";
 
% my $tx = $ua->get("http://$agentHostname:3001/db/rename?$param");
 
% my $body = '{"result":"mistake"}';
% eval { $body = $tx->result->body };
% my $res = decode_json($body);
 
% do {
%   $model->jobUpdate($jobID, status => 'done', error => 'noerr', stop => $model->timestamp);
    <div class="callout success">
        Agent <%= $agentHostname %> rename database <%= $dbname %> to <%= $newname %> as job <%= $jobID %>.
    </div>
%   $model->agentDBUpdate($agentID);
% } if $res->{'result'} eq 'success';
 
% do {
%   $model->jobUpdate($jobID, status => 'pushjob', error => 'pusherr', stop => $model->timestamp);
    <div class="callout alert">
        Agent <%= $agentHostname %> cannot rename <%= $dbname %>. 
        Job <%= $jobID %> was cancel.
    </div>
% } if $res->{'result'} ne 'success';
 
%#EOF

dbRestore.html.ep

dbRestore.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
%#EOF

exception.development.html.ep

exception.development.html.ep
%#
%# $Id: exception.html.ep 627 2017-04-15 13:02:08Z ziggi $
%#
% layout 'default';
% title 'Error';
 
<h5>Oops... Exception</h5>
 
<pre>  
%= $exception
</pre>
 
%#EOF

exception.production.html.ep

exception.production.html.ep
%#
%# $Id: exception.html.ep 627 2017-04-15 13:02:08Z ziggi $
%#
% layout 'default';
% title 'Error';
 
<h5>Oops... Exception</h5>
 
<pre>
%= $exception
</pre>
 
%#EOF

hello.html.ep

hello.html.ep
% layout 'default';
% title 'Dumper Hello';
% use Mojo::Util qw(b64_encode b64_decode md5_sum dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
<h5>Hello, body! How are you there?</h5>
 
%#EOF

jobList.html.ep

jobList.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use utf8;
% use strict;
% use Switch;
% use Mojo::Util qw(dumper);
 
% my $model = $self->app->model;
% my $jobList = $model->jobList;
 
 
 
<div class="text-center">
    <h5>
        Job list
        <a href="/job/list"><i class="fi-refresh" style="font-size:1.3rem;"></i></a>
    </h5>
</div>
 
<table id="table" class="display" >
    <thead>
      <tr>
        <td>#</td>
        <td>type</td>
        <td>status</td>
        <td>err</td>
        <td>source</td>
        <td>dest</td>
        <td>start</td>
        <td>last</td>
      </tr>
    </thead>
    <tbody>
% my $num = 1;
% foreach my $job (@{$jobList}) {
%   my $jobID = $job->{'id'};
%   my $type = $job->{'type'};
%   my $sourceID = $job->{'sourceid'};
%   my $destID = $job->{'destid'};
%   my $sourcehost = '';
%   my $desthost = '';
%   my $status = $job->{'status'};
%   my $start = $job->{'begin'};
%   my $stop = $job->{'stop'};
%   my $error = $job->{'error'} || 'noerr';
 
%   if ($type eq 'dbdump') { 
%      $sourcehost = $model->agentProfile($sourceID)->{'hostname'} if $sourceID;
%      $desthost = $model->storeProfile($destID)->{'hostname'} if $destID;
%   }
 
%   if ($type eq 'dbcopy' || $type eq 'dbrename' || $type eq 'dbdrop') { 
%      $sourcehost = $model->agentProfile($sourceID)->{'hostname'} if $sourceID;
%      $desthost = '';
%   }
 
%  $error = '' if ($error eq 'noerr' || $error eq 'undef');
%  $stop = '' if ($stop =~ /1970-01-01/);
 
    <tr>
        <td><%= $jobID %></td>
        <td><%= $type %></td>
        <td><%= $status %></td>
        <td><%= $error %></td>
        <td><%= $sourcehost %></td>
        <td><%= $desthost %></td>
        <td><%= $start %></td>
        <td><%= $stop %></td>
    </tr>
%   $num++;
% }
    </tbody>
</table>
 
<script>
$.extend(true, $.fn.dataTable.defaults, {
    "searching": false,
    "ordering": true,
    "lengthChange": false,
});
 
%#$(document).ready(function() {
%#    $('#table').DataTable({
%#        "info": false,
%#        "processing": true
%#    });
%#});
</script>
 
 
%#EOF

not_found.development.html.ep

not_found.development.html.ep
%#
%# $Id: not_found.html.ep 627 2017-04-15 13:02:08Z ziggi $
%#
% layout 'default';
% title 'Dumper';
 
<h5>404 Page not found</h5>
 
%#EOF

not_found.production.html.ep

not_found.production.html.ep
%#
%# $Id: not_found.html.ep 627 2017-04-15 13:02:08Z ziggi $
%#
% layout 'default';
% title 'Dumper';
 
<h5>404 Page not found</h5>
 
%#EOF

storeAdd.html.ep

storeAdd.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% my $hostname = $req->param('hostname');
% my $username = $req->param('username');
% my $password = $req->param('password');
 
% my $id = $self->app->model->storeExist($hostname);
% do {
    <div class="callout alert">
        Store <%= $hostname %> yet exist.
    </div>
% } if $id;
 
% do {
%   my $id = $self->app->model->storeAdd($hostname, $username, $password);
%   do {
      <div class="callout success">
        Store <%= $hostname %> was added successful with id <%= $id %>.
      </div>
%   } if $id;
%   do {
      <div class="callout alert">
          Store <%= $hostname %> was added unsuccessful =(
      </div>
%   } unless $id;
% } unless $id;
 
<div class="text-center">
    <a class="button" href="/store/list">List</a>
    &nbsp;&nbsp;
    <a class="button" href="/store/form/add">Add yet</a>
</div>
%#EOF

storeDataList.html.ep

storeDataList.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% my $storeID = $req->param('id');
% my $storeHostname = $self->app->model->storeHostname($storeID);
 
% my $storeAlive = $self->app->model->storeAlive($storeID);
% unless ($storeAlive) {
    <div class="callout alert">
        Oops.. Store <%= $storeHostname %> not respond.
        Please, check store configuration. 
        Now will use cached dataset list.
    </div>
% }
 
% if ($storeAlive) {
%     $self->app->model->storeDataUpdate($storeID);
% }
 
% my $dataList = $self->app->model->storeDataList($storeID);
 
<div class="text-center">
    <h5>
        Store <%= $storeHostname%> dataset list&nbsp;
        <a href="/store/datalist?id=<%= $storeID %>">
            <i class="fi-refresh" style="font-size:1.3rem;"></i>
        </a>
    </h5>
</div>
 
<table id="table" class="display" >
    <thead>
      <tr>
        <td>#</td>
        <td>dbname</td>
        <td>date</td>
        <td>size</td>
        <td>source</td>
      </tr>
    </thead>
    <tbody>
% my $num = 1;
% foreach my $data (@{$dataList}) {
%   my $filename = $data->{'name'};
%   my $dbname = $data->{'dbname'};
%   my $stamp = $data->{'stamp'};
%   my $size = $data->{'size'};
%   my $source = $data->{'source'};
    <tr>
        <td><%= $num %></td>
        <td><%= $dbname %></td>
        <td><%= $stamp %></td>
        <td><%= $size %></td>
        <td><%= $source %></td>
    </tr>
%   $num++;
% }
    </tbody>
</table>
 
<script>
$.extend(true, $.fn.dataTable.defaults, {
    "searching": true,
    "ordering": true,
    "lengthChange": false
} );
 
$(document).ready(function() {
    $('#table').DataTable();
});
</script>
 
 
%#EOF

storeDataUpdate.html.ep

storeDataUpdate.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% my $id = $req->param('id');
 
% my $model = $self->app->model;
% my $storeHostname = $model->storeHostname($id);
% my $res = $model->hostnameAlive($storeHostname);
 
% my $dataList;
% do { $dataList = $model->storeDataUpdate($id); } if $res;
 
% do {
    <div class="callout alert">
        Oops...Store hostname <%= $storeHostname %> not resolved.
    </div>
% } unless $dataList;
 
% do {
    <div class="callout success">
        Store <%= $storeHostname %> data list updated
    </div>
% } if $dataList;
 
<div class="text-center">
    <a class="button hollow" href="/store/dataupdate?id=<%= $id %>">DataUpdate</a>
    <a class="button hollow" href="/store/form/update?id=<%= $id %>">Reconf</a>
    <a class="button hollow" href="/store/datalist?id=<%= $id %>">DataList</a>
</div>
 
% do {
    <table id="table" class="display" >
        <thead>
          <tr>
            <td>#</td>
            <td>name</td>
            <td>size</td>
          </tr>
        </thead>
        <tbody>
%     my $num = 1;
%     foreach my $db (@{$dataList}) {
%       my $name = $db->{'name'};
%       my $size = $db->{'size'};
%       my $owner = $db->{'owner'};
        <tr>
            <td><%= $num %></td>
            <td><%= $name %></td>
            <td><%= $size %></td>
        </tr>
%       $num++;
%     }
        </tbody>
    </table>
% } if $dataList;
%#EOF

storeDelete.html.ep

storeDelete.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
% my $id = $req->param('id');
% my $res = $self->app->model->storeDelete($id);
 
% do {
    <div class="callout success">
        Store id <%= $id %> was deleted successful.
    </div>
% } if $res;
 
% do {
    <div class="callout alert">
        Store id <%= $id %> was deleted unsuccessful.
    </div>
% } unless $res;
 
<div class="text-center">
    <a class="button" href="/store/list">List</a>
    &nbsp;&nbsp;&nbsp;
    <a class="button" href="/store/form/add">Add</a>
</div>
%#EOF

storeFormAdd.html.ep

storeFormAdd.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
<div class="row columns">
<form accept-charset="UTF-8" id="user-create-form" action="/store/add" method="post" data-abide novalidate>
    <h5>Add DB store</h5>
 
    <label>hostname
        <input type="text" name="hostname" placeholder="store hostname" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{3,64}"/>
        <span class="form-error">mandatory, 3 or more letter</span>
    </label>
 
    <label>username
        <input type="text" name="username" placeholder="login" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
        <span class="form-error">mandatory, 5 or more letter</span>
    </label>
 
    <label>password
        <input type="text" name="password" placeholder="password" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
        <span class="form-error">mandatory, 5 or more letter</span>
    </label>
    <p class="text-center">
        <button type="submit" class="button">Accept</button>
    </p>
</form>
</div>
 
%#EOF

storeFormUpdate.html.ep

storeFormUpdate.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
% my $id = $req->param('id');
% my $store = pop @{$self->app->model->storeList($id)};
 
% my $hostname = $store->{'hostname'};
% my $username = $store->{'username'};
% my $password = $store->{'password'};
 
<form accept-charset="UTF-8" action="/store/update" method="post" data-abide novalidate>
        <h5>Update DB store <%= $store->{"hostname"} %></h5>
        <input type="hidden" name="id" value="<%= $id %>"/>
 
        <label>hostname
            <input type="text" name="hostname" placeholder="hostname" value="<%= $hostname %>" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
            <span class="form-error">mandatory parameter, 5 or more letter</span>
        </label>
 
        <label>username
            <input type="text" name="username" placeholder="username" value="<%= $username %>" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
            <span class="form-error">mandatory, 5 or more letter</span>
        </label>
 
        <label>password
            <input type="password" name="password" placeholder="password" value="<%= $password %>" required pattern="[a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{5,42}"/>
            <span class="form-error">mandatory, 5 or more letter</span>
        </label>
 
        <p class="text-center">
            <button type="submit" class="button">Accept</button>
        </p>
</form>
 
%#EOF

storeList.html.ep

storeList.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(b64_encode b64_decode md5_sum dumper);
% use Encode qw(decode encode);
 
% use utf8;
% use strict;
 
% my $storeList = $self->app->model->storeList;
 
% foreach my $store (@{$storeList}) {
%   my $id = $store->{'id'};
%   my $hostname = $store->{'hostname'};
    <div class="reveal" id="modal-<%= $id %>" data-reveal>
        <div class="text-center">
            <h5>Store <%= $hostname %></h5>
            <div class="stacked-for-small button-group">
                <a class="button" href="/store/form/update?id=<%= $id %>">Reconf</a>
                <a class="button" href="/store/datalist?id=<%= $id %>">DataList</a>
                <a class="button" href="/store/dataupdate?id=<%= $id %>">DataUpdate</a>
                <a class="button alert" href="#" data-open="modal-delete-<%= $id %>">Delete</a>
            </div>
        </div>
        <button class="close-button" data-close aria-label="Close modal" type="button">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
 
    <div class="reveal" id="modal-delete-<%= $id %>" data-reveal>
        <div class="text-center">
            <h5>Do you want to delete agent <%= $hostname %>?</h5>
            <a class="button hollow expanded" data-close aria-label="Close modal">Escape</a>
            <a class="button hollow expanded alert" href="/store/delete?id=<%= $id %>">Delete</a>
        </div>
        <button class="close-button" data-close aria-label="Close modal" type="button">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
% }
 
<table id="table" class="display" >
    <thead>
      <tr>
        <td>#</td>
        <td>hostname</td>
      </tr>
    </thead>
    <tbody>
% my $num = 1;
% foreach my $store (@{$storeList}) {
%   my $id = $store->{'id'};
%   my $hostname = $store->{'hostname'};
    <tr>
        <td><%= $num %></td>
        <td><a href="#" data-open="modal-<%= $id %>"><%= $hostname %></a></td>
    </tr>
%   $num++;
% }
    </tbody>
</table>
%#EOF

storeUpdate.html.ep

storeUpdate.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% my $id = $req->param('id');
% my $hostname = $req->param('hostname');
% my $username = $req->param('username');
% my $password = $req->param('password');
 
% my $res = $self->app->model->storeUpdate($id, $hostname, $username, $password);
 
% do {
    <div class="callout success">
        Store <%= $hostname %> was updated successful.
    </div>
% } if $res;
 
% do {
    <div class="callout alert">
        Store <%= $hostname %> was updated unsuccessful.
    </div>
% } unless $res;
 
<div class="text-center">
    <a class="button" href="/store/list">List</a>
    &nbsp;&nbsp;&nbsp;
    <a class="button" href="/store/form/add">Add</a>
</div>
%#EOF

template.html.ep

template.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'Dumper';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
%#EOF