The work still in progress
#!@PERL@ #------------ #--- AUTH --- #------------ package PGagent::BasicAuth; use strict; use warnings; use POSIX qw(getpid setuid setgid geteuid getegid); use Cwd qw(cwd getcwd chdir); use Mojo::Util qw(md5_sum b64_decode dumper); use Apache::Htpasswd; sub new { my ($class, $pwdfile) = @_; my $self = { pwdfile => $pwdfile, errstr => undef }; bless $self, $class; return $self; } sub pwdfile { my ($self, $pwdfile) = @_; return $self->{pwdfile} unless $pwdfile; $self->{pwdfile} = $pwdfile if $pwdfile; $self; } sub auth { my ($self, $authstr) = @_; return undef unless $authstr; my $hash = $self->split($authstr); return undef unless $hash; return undef unless -r $self->{pwdfile}; my $res = undef; eval { my $ht = Apache::Htpasswd->new( { passwdFile => $self->pwdfile, ReadOnly => 1 } ); $res = $ht->htCheckPassword( $hash->{username}, $hash->{password} ); }; return undef if $@; $res; } sub username { my ($self, $authstr) = @_; return undef unless $authstr; my $hash = $self->split($authstr); return undef unless $hash; $hash->{username} if $hash; } sub split { my ($self, $authstr) = @_; return undef unless $authstr; my ($type, $enc) = split /\s+/, $authstr; return undef unless ($type eq 'Basic' && $enc); my ($username, $password) = split /:/, b64_decode($enc); return undef unless ($username && $password); { username => $username, password => $password }; } 1; #-------------- #--- DAEMON --- #-------------- package PGagent::Daemon; use strict; use warnings; use POSIX qw(getpid setuid setgid geteuid getegid); use Cwd qw(cwd getcwd chdir); use Mojo::Util qw(dumper); sub new { my $class = shift; my $self = { pid => undef }; bless $self, $class; return $self; } sub fork { my ($self, $user, $group) = shift; my $pid = fork; if ($pid > 0) { exit; } chdir("/"); open(my $stdout, '>&', STDOUT); open(my $stderr, '>&', STDERR); open(STDOUT, '>>', '/dev/null'); open(STDERR, '>>', '/dev/null'); $self->pid(getpid); $self->pid; } sub pid { my ($self, $pid) = @_; return $self->{pid} unless $pid; $self->{pid} = $pid if $pid; $self; } 1; #---------- #--- DB --- #---------- package PGagent::DB; use strict; use warnings; use DBI; sub new { my ($class, %args) = @_; my $self = { hostname => $args{hostname} || 'localhost', username => $args{username} || 'postgres', password => $args{password} || 'password', database => $args{database} || 'postgres', engine => 'Pg', error => '' }; bless $self, $class; return $self; } sub username { my ($self, $username) = @_; return $self->{username} unless $username; $self->{username} = $username; $self; } sub password { my ($self, $password) = @_; return $self->{password} unless $password; $self->{password} = $password; $self; } sub hostname { my ($self, $hostname) = @_; return $self->{hostname} unless $hostname; $self->{hostname} = $hostname; $self; } sub database { my ($self, $database) = @_; return $self->{database} unless $database; $self->{database} = $database; $self; } sub error { my ($self, $error) = @_; return $self->{error} unless $error; $self->{error} = $error; $self; } sub engine { my ($self, $engine) = @_; return $self->{engine} unless $engine; $self->{engine} = $engine; $self; } sub exec { my ($self, $query) = @_; return undef unless $query; my $dsn = 'dbi:'.$self->engine. ':dbname='.$self->database. ';host='.$self->hostname; my $dbi; eval { $dbi = DBI->connect($dsn, $self->username, $self->password, { RaiseError => 1, PrintError => 0, AutoCommit => 1 }); }; $self->error($@); return undef if $@; my $sth; eval { $sth = $dbi->prepare($query); }; $self->error($@); return undef if $@; my $rows = $sth->execute; my @list; while (my $row = $sth->fetchrow_hashref) { push @list, $row; } $sth->finish; $dbi->disconnect; \@list; } sub do { my ($self, $query) = @_; return undef unless $query; my $dsn = 'dbi:'.$self->engine. ':dbname='.$self->database. ';host='.$self->hostname; my $dbi; eval { $dbi = DBI->connect($dsn, $self->username, $self->password, { RaiseError => 1, PrintError => 0, AutoCommit => 1 }); }; $self->error($@); return undef if $@; my $rows; eval { $rows = $dbi->do($query) or return undef; }; $self->error($@); return undef if $@; $dbi->disconnect; $rows*1; } 1; #------------- #--- MODEL --- #------------- package PGagent::Model; use strict; use warnings; use File::stat; use Data::Dumper; use DBI; use Mojo::UserAgent; use Mojo::Util qw(dumper); use Mojo::JSON qw(encode_json decode_json true false); use POSIX; use Socket; #sub new { # my ($class, $app, $dbhost, $dbuser, $dbpwd) = @_; # my $self = { # app => $app, # db => PGagent::DB->new( # hostname => $dbhost, # username => $dbuser, # password => $dbpwd, # database => 'postgres', # ) # }; # bless $self, $class; # return $self; #} sub new { my ($class, $app, $db) = @_; my $self = { app => $app, db => $db }; bless $self, $class; return $self; } sub app { shift->{app}; } sub db { shift->{db}; } sub log { shift->app->log; } sub hello { "hello!"; } sub pgpasswd { shift->db->password; } sub pguser { shift->db->username; } sub pghost { shift->db->hostname; } sub store_alive { my ($self, $hostname) = @_; return undef unless $hostname; my $ua = Mojo::UserAgent->new; my $tx = $ua->get("https://$hostname:3002/hello"); $ua = $ua->connect_timeout(5)->request_timeout(5);; my $res; eval { $res = decode_json($tx->result->body); }; return 1 if $res->{'message'} eq 'hello'; return undef; } sub store_put { my ($self, $datafile, $storename, $storeuser, $storepwd) = @_; my $ua = Mojo::UserAgent->new(max_redirects => 5); $ua = $ua->connect_timeout(15)->request_timeout(15);; return undef unless $datafile; return undef unless $storename; return undef unless -f $datafile; return undef unless -r $datafile; return undef unless $self->store_alive($storename); $self->app->log->info("store_put: Start upload $datafile to store $storename"); my $tx = $ua->post("https://$storeuser:$storepwd\@$storename:3002/data/put" => form => { data => { file => $datafile }} ); $self->app->log->info("store_put: End upload $datafile to store $storename"); my $res; eval { $res = $tx->result; }; return $res->body if $res; return undef; } sub store_get { my ($self, $dataset, $storename, $storeuser, $storepwd) = @_; return undef unless $dataset; return undef unless $storename; return undef unless $self->store_alive($storename); $storeuser = 'undef' unless $storeuser; $storepwd = 'undef' unless $storepwd; my $tmpdir = $self->app->config('tmpdir'); return undef unless -d $tmpdir; return undef unless -w $tmpdir; my $datafile = "$tmpdir/rest".."$dataset"; $self->app->log->info("store_get: Start download $dataset from store $storename to $datafile"); my $ua = Mojo::UserAgent->new(max_redirects => 5); $ua = $ua->connect_timeout(30)->request_timeout(30);; my $tx = $ua->get("https://$storeuser:$storepwd\@$storename:3002/data/get?dataname=$dataset"); $tx->result->content->asset->move_to($datafile); $self->app->log->info("store_get: End download $dataset from store $storename"); return $datafile if -r $datafile; unlink $datafile; return undef; } #----------------- #--- DB MODEL ---- #----------------- sub db_exist { my ($self, $dbname) = @_; return undef unless $dbname; my $dblist = $self->db_list; foreach my $db (@$dblist) { return 1 if $db->{"name"} eq $dbname; } return undef; } sub db_size { my ($self, $dbname) = @_; return undef unless $dbname; return undef unless $self->db_exist($dbname); my $query = "select pg_database_size('$dbname') as size;"; my $res = $self->db->exec($query); $res->[0]{size}; } sub db_list { my $self = shift; my $query = "select d.datname as name, pg_database_size(d.datname) as size, u.usename as owner, s.numbackends as numbackends from pg_database d, pg_user u, pg_stat_database s where d.datdba = u.usesysid and d.datname = s.datname order by d.datname;"; $self->db->exec($query); } sub db_create { my ($self, $dbname) = @_; return undef unless $dbname; return undef if $self->db_exist($dbname); my $query = "create database $dbname"; $self->db->do($query); $self->db_exist($dbname); } sub db_drop { my ($self, $dbname) = @_; return undef unless $dbname; return undef unless $self->db_exist($dbname); my $query = "drop database $dbname"; $self->db->do($query); return undef if $self->db_exist($dbname); $dbname; } sub db_rename { my ($self, $dbname, $newname) = @_; return undef unless $dbname; return undef unless $newname; return undef unless $self->db_exist($dbname); my $query = "alter database $dbname rename to $newname;"; $self->db->do($query); $self->db_exist($newname); } sub db_copy { my ($self, $dbname, $newname) = @_; return undef unless $dbname; return undef unless $newname; return undef unless $self->db_exist($dbname); return undef if $self->db_exist($newname); my $query = "create database $newname template $dbname;"; $self->db->do($query); $self->db_exist($newname); } sub db_dump { my ($self, $dbname, $hostname, $tmpdir) = @_; return undef unless $dbname; return undef unless $self->db_exist($dbname); return undef unless -d $tmpdir; return undef unless -w $tmpdir; my $timestamp = strftime("%Y%m%d-%H%M%S-%Z", localtime(time)); my $dbdump = "$tmpdir/$dbname--$timestamp--$hostname.sqlz"; my $password = $self->pgpasswd; my $pghost = $self->pghost; my $username = $self->pguser; my $out = qx/PGPASSWORD=$password pg_dump -h $pghost -U $username -Fc -f $dbdump $dbname 2>&1/; my $retcode = $?; return $dbdump if $retcode == 0; undef; } sub db_restore { my ($self, $filename, $dbname) = @_; return undef unless $dbname; return undef unless $filename; return undef unless -r $filename; return undef unless -s $filename; return undef if $self->db_exist($dbname); return undef unless $self->db_create($dbname); my $password = $self->pgpasswd; my $pghost = $self->pghost; my $username = $self->pguser; my $out = qx/PGPASSWORD=$password pg_restore -j 4 -h $pghost -U $username -Fc -d $dbname $filename 2>&1/; my $retcode = $?; if ($retcode > 1) { $self->db_drop($dbname) if $self->db_exist($dbname); return undef; } $dbname; } sub db_owner { my ($self, $dbname) = @_; return undef unless $dbname; return undef unless $self->db_exist($dbname); 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"; $self->db->exec($query); } #------------------- #--- ROLE MODEL ---- #------------------- sub role_list { my $self = shift; my $query = "select usename as rolename from pg_user order by rolename"; $self->db->exec($query); } sub role_exist { my ($self, $role) = @_; return undef unless $role; my $list = $self->role_list; foreach my $name (@{$self->role_list}) { return $name->{rolename} if $name->{rolename} eq $role; } return undef; } sub role_create { my ($self, $rolename, $password) = @_; return undef unless $password; return undef unless $rolename; return undef unless $self->role_exist($rolename); my $query = "create user '$rolename' encrypted password '$password';"; $self->db->do($query); $self->role_exist($rolename); } sub role_drop { my ($self, $rolename) = @_; return undef unless $rolename; return undef unless $self->role_exist($rolename); my $query = "drop user '$rolename';"; $self->db->do($query); return undef if $self->role_exist($rolename); $rolename; } sub role_password { my ($self, $rolename, $password) = @_; return undef unless $password; return undef unless $rolename; return undef unless $self->role_exist($rolename); my $query = "alter role $rolename encrypted password '$password'"; $self->db->do($query); $self->role_exist($rolename); } 1; #------------------ #--- CONTROLLER --- #------------------ package PGagent::Controller; use utf8; use strict; use warnings; use Mojo::Base 'Mojolicious::Controller'; use Mojo::JSON qw(encode_json decode_json true false); use Mojo::Util qw(dumper); use Mojo::IOLoop::Subprocess; use Apache::Htpasswd; sub hello { my $self = shift; $self->render(json => { message => 'hello', success => true }); } sub db_list { my $self = shift; $self->render(json => { dblist => $self->app->model->db_list, success => true }); } sub db_size { my $self = shift; my $dbname = $self->req->param('dbname'); return $self->render(json => { success => false, dbname => $dbname }) unless $dbname; my $dbsize = $self->app->model->db_size($dbname); $self->render(json => { name => $dbname, size => $dbsize }); } sub db_create { my $self = shift; my $dbname = $self->req->param('dbname'); return $self->render(json => { success => false }) unless $dbname; my $res = $self->app->model->db_create($dbname); return $self->render(json => { success => true }) if $res; $self->render(json => { success => false }); } sub db_drop { my $self = shift; my $dbname = $self->req->param('dbname'); return $self->render(json => { success => false }) unless $dbname; my $res = $self->app->model->db_drop($dbname); return $self->render(json => { success => false }) unless $res; return $self->render(json => { success => true }); } sub db_rename { my $self = shift; my $dbname = $self->req->param('dbname'); my $newname = $self->req->param('newname'); return $self->render(json => { success => false }) unless $dbname; return $self->render(json => { success => false }) unless $newname; my $res = $self->app->model->db_rename($dbname, $newname); return $self->render(json => { success => true }) if $res; return $self->render(json => { success => false }); } sub db_copy { my $self = shift; my $dbname = $self->req->param('dbname'); my $newname = $self->req->param('newname'); return $self->render(json => { success => false }) unless $dbname; return $self->render(json => { success => false }) unless $newname; my $res = $self->app->model->db_copy($dbname, $newname); my $dbsize = $self->app->model->db_size($dbname) if $res; return $self->render(json => { success => true, dbsize => $dbsize}) if $res; return $self->render(json => { success => false}); } sub db_exist { my $self = shift; my $dbname = $self->req->param('dbname'); return $self->render( json => { success => false, dbname => '' } ) unless $dbname; my $res = $self->app->model->db_exist($dbname); my $dbsize = $self->app->model->db_size($dbname) if $res; return $self->render( json => { success => true, dbname => $dbname, dbsize => $dbsize} ) if $res; return $self->render( json => { success => false, dbname => $dbname } ); } sub master_cb { my ($self, $master, $job_id, $magic, $status, $error, $result) = @_; return undef unless $master; return undef unless $job_id; return undef unless $magic; return undef unless $status; $error = 'noerr' unless $error; $result = 'success' unless $result; my $ua = Mojo::UserAgent->new; $ua = $ua->connect_timeout(15)->request_timeout(15);; my $param; $param .= "&master=$master"; $param .= "&jobid=$job_id"; $param .= "&magic=$magic"; $param .= "&status=$status"; $param .= "&success=true"; my $url = "https://$master:3003/agent/job/cb?$param"; $self->app->log->debug("master_cb: master callback $url"); my $tx = $ua->get($url); my $body; eval { $body = $tx->result->body }; my $res; eval { $res = decode_json($body); }; return $res if $res; return undef; } sub db_dump { 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 $job_id = $self->req->param('jobid'); my $magic = $self->req->param('magic'); my $hostname = $self->app->config('hostname'); my $tmpdir = $self->app->config('tmpdir'); return $self->render( json => { success => false, dbname => '' } ) unless $dbname; return $self->render( json => { success => false, dbname => $dbname } ) unless $self->app->model->db_exist($dbname); my $subprocess = Mojo::IOLoop::Subprocess->new; $self->app->log->info("db_dump: Begin dump database $dbname in subprocess"); $subprocess->run( sub { my $subprocess = shift; $self->app->log->info("db_dump subprocess: Begin dump database $dbname"); my $filename = $self->app->model->db_dump($dbname, $hostname, $tmpdir); $self->app->log->info("db_dump subprocess: Done dump database $dbname, result is file $filename"); unless ($filename) { unlink $filename; $self->master_cb($master, $job_id, $magic, "dumperr", "dumperr", 'mistake'); return undef; } my $res_cb = $self->master_cb($master, $job_id, $magic, "dumped", "noerr", 'success'); $self->app->log->info("db_dump subprocess: Begin store database $dbname"); my $store_res = $self->app->model->store_put($filename, $storename, $storelogin, $storepwd); $self->app->log->info("db_dump subprocess: Done store database $dbname with store responce $store_res"); unless ($store_res) { unlink $filename; $self->master_cb($master, $job_id, $magic, "storeerr", "storeerr", 'mistake'); return undef; } $self->master_cb($master, $job_id, $magic, "stored", "noerr", 'success'); unlink $filename; 1; }, sub { my ($subprocess, $err, @results) = @_; my $pid = $subprocess->pid; $self->app->log->info("db_dump: End dump subprocess $pid for dump database $dbname"); } ); $subprocess->ioloop->start unless $subprocess->ioloop->is_running; $self->render(json => { success => true, dbname => $dbname } ); } sub db_restore { my $self = shift; my $req = $self->req; my $dataname = $req->param('dataname'); my $storename = $req->param('store'); my $storelogin = $req->param('storelogin'); my $storepwd = $req->param('storepwd'); my $newname = $req->param('newname'); my $master = $req->param('master'); my $job_id = $req->param('jobid'); my $magic = $req->param('magic'); my $m = $self->app->model; return $self->render(json => { success => false }) unless ($dataname and $storename and $storelogin); return $self->render(json => { success => false }) unless ($storepwd and $newname and $job_id and $master and $magic); return $self->render(json => { success => false }) unless $m->store_alive($storename); my $subprocess = Mojo::IOLoop::Subprocess->new; $subprocess->run( sub { my $subprocess = shift; $self->master_cb($master, $job_id, $magic, "dataget", "noerr", 'success'); my $datafile = $m->store_get($dataname, $storename, $storelogin, $storepwd); unless ($datafile || -s $datafile) { unlink $dataname; $self->master_cb($master, $job_id, $magic, "geterr", "geterr", 'mistake'); return undef; } $self->master_cb($master, $job_id, $magic, "datagot", "noerr", 'success'); my $newdbname = $m->db_restore($datafile, $newname); unless ($newdbname) { unlink $dataname; $self->master_cb($master, $job_id, $magic, "resterr", "resterr", 'mistake'); return undef; }; $self->master_cb($master, $job_id, $magic, "done", "noerr", 'success'); 1; }, sub { my ($subprocess, $err, @results) = @_; my $pid = $subprocess->pid; $self->app->log->info("db_restore: End restore subprocess $pid for dataset $dataname"); } ); $subprocess->ioloop->start unless $subprocess->ioloop->is_running; $self->render(json => { success => true }); } sub role_list { my $self = shift; $self->render(json => { success => true, role => $self->app->model->role_list }); } sub role_exist { my $self = shift; my $rolename = $self->req->param('rolename'); return $self->render(json => { success => false, rolename => undef }) unless $rolename; my $res = $self->app->model->role_exist($rolename); return $self->render(json => { success => true, rolename => $rolename }) if $res; $self->render(json => { success => false, rolename => $rolename }); } sub role_password { my $self = shift; my $rolename = $self->req->param('rolename'); return $self->render(json => { success => false, rolename => undef }) unless $rolename; # dummy $self->render(json => { success => true, rolename => $rolename }); } sub role_create { my $self = shift; my $rolename = $self->req->param('rolename'); return $self->render(json => { success => false, rolename => undef }) unless $rolename; $self->render(json => { success => true, rolename => $rolename }); } sub role_drop { my $self = shift; my $rolename = $self->req->param('rolename'); return $self->render(json => { success => false, rolename => undef }) unless $rolename; $self->render(json => { success => true, rolename => $rolename }); } 1; #----------- #--- APP --- #----------- package PGagent; use strict; use warnings; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; } 1; #------------ #--- MAIN --- #------------ use strict; use warnings; use POSIX qw(setuid setgid tzset tzname strftime); use Mojo::Server::Prefork; use Mojo::IOLoop::Subprocess; use Mojo::Util qw(md5_sum b64_decode 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 $appname = 'pgdumper'; #-------------- #--- GETOPT --- #-------------- getopt 'h|help' => \my $help, 'c|config=s' => \my $conffile, 'f|nofork' => \my $nofork, 'u|user=s' => \my $user, 'g|group=s' => \my $group; if ($help) { print qq( Usage: app [OPTIONS] Options -h | --help This help -c | --config=path Path to config file -u | --user=user System owner of process -g | --group=group System group -f | --nofork Dont fork process The options override options from configuration file )."\n"; exit 0; } #------------------ #--- APP CONFIG --- #------------------ my $server = Mojo::Server::Prefork->new; my $app = $server->build_app('PGagent'); $app = $app->controller_class('PGagent::Controller'); $app->secrets(['6d578e43ba88260e0375a1a35fd7954b']); $app->static->paths(['@APP_LIBDIR@/public']); $app->renderer->paths(['@APP_LIBDIR@/templs']); $app->config(conffile => $conffile || '@APP_CONFDIR@/pgagent.conf'); $app->config(pwdfile => '@APP_CONFDIR@/pgagent.pw'); $app->config(logfile => '@APP_LOGDIR@/pgagent.log'); $app->config(loglevel => 'debug'); $app->config(pidfile => '@APP_RUNDIR@/pgagent.pid'); $app->config(crtfile => '@APP_CONFDIR@/pgagent.crt'); $app->config(keyfile => '@APP_CONFDIR@/pgagent.key'); $app->config(user => $user || '@APP_USER@'); $app->config(group => $group || '@APP_GROUP@'); $app->config(listenaddr4 => '0.0.0.0'); $app->config(listenaddr6 => '[::]'); $app->config(listenport => '3001'); $app->config(tmpdir => '/tmp'); $app->config(pghost => '127.0.0.1'); $app->config(pguser => 'postgres'); $app->config(pgpwd => 'password'); $app->config(hostname => hostname); if (-r $app->config('conffile')) { $app->log->debug("Load configuration from ".$app->config('conffile')); $app->plugin('JSONConfig', { file => $app->config('conffile') }); } #--------------- #--- HELPERS --- #--------------- $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); }); $app->helper( model => sub { my $db = PGagent::DB->new( hostname => $app->config("pghost"), username => $app->config("pguser"), password => $app->config("pgpwd"), database => 'postgres' ); state $model = PGagent::Model->new($app, $db); } ); #-------------- #--- ROUTES --- #-------------- my $r = $app->routes; $r->add_condition( auth => sub { my ($route, $c) = @_; my $log = $c->app->log; my $authstr = $c->req->headers->authorization; my $pwdfile = $c->app->config('pwdfile'); my $a = PGagent::BasicAuth->new($pwdfile); $log->info("Try auth user ". $a->username($authstr)); $a->auth($authstr); } ); $r->any('/hello')->to('controller#hello'); $r->any('/conf/dump')->over('auth')->to('controller#conf_dump'); $r->any('/db/list') ->over('auth')->to('controller#db_list'); $r->any('/db/create') ->over('auth')->to('controller#db_create'); $r->any('/db/drop') ->over('auth')->to('controller#db_drop'); $r->any('/db/rename') ->over('auth')->to('controller#db_rename'); $r->any('/db/copy') ->over('auth')->to('controller#db_copy'); $r->any('/db/size') ->over('auth')->to('controller#db_size'); $r->any('/db/dump') ->over('auth')->to('controller#db_dump'); $r->any('/db/restore') ->over('auth')->to('controller#db_restore'); $r->any('/db/exist') ->over('auth')->to('controller#db_exist'); $r->any('/db/owner') ->over('auth')->to('controller#db_owner'); $r->any('/role/list') ->over('auth')->to('controller#role_list'); $r->any('/role/exist') ->over('auth')->to('controller#role_exist'); $r->any('/role/password')->over('auth')->to('controller#role_password'); $r->any('/role/create') ->over('auth')->to('controller#role_create'); $r->any('/role/drop') ->over('auth')->to('controller#role_drop'); #---------------- #--- LISTENER --- #---------------- my $tls = '?'; $tls .= 'cert='.$app->config('crtfile'); $tls .= '&key='.$app->config('keyfile'); my $listen4; if ($app->config('listenaddr4')) { $listen4 = "https://"; $listen4 .= $app->config('listenaddr4').':'.$app->config('listenport'); $listen4 .= $tls; } my $listen6; if ($app->config('listenaddr6')) { $listen6 = "https://"; $listen6 .= $app->config('listenaddr6').':'.$app->config('listenport'); $listen6 .= $tls; } my @listen; push @listen, $listen4 if $listen4; push @listen, $listen6 if $listen6; $server->listen(\@listen); $server->heartbeat_interval(3); $server->heartbeat_timeout(60); #-------------- #--- DAEMON --- #-------------- unless ($nofork) { my $d = PGagent::Daemon->new; my $user = $app->config('user'); my $group = $app->config('group'); $d->fork; $app->log(Mojo::Log->new( path => $app->config('logfile'), level => $app->config('loglevel') )); } $server->pid_file($app->config('pidfile')); #--------------- #--- WEB LOG --- #--------------- $app->hook(before_dispatch => sub { my $c = shift; my $remote_address = $c->tx->remote_address; my $method = $c->req->method; my $base = $c->req->url->base->to_string; my $path = $c->req->url->path->to_string; my $loglevel = $c->app->log->level; my $url = $c->req->url->to_abs->to_string; unless ($loglevel eq 'debug') { #$c->app->log->info("$remote_address $method $base$path"); $c->app->log->info("$remote_address $method $url"); } if ($loglevel eq 'debug') { $c->app->log->debug("$remote_address $method $url"); } }); #---------------------- #--- SIGNAL HANDLER --- #---------------------- local $SIG{HUP} = sub { $app->log->info('Catch HUP signal'); $app->log(Mojo::Log->new( path => $app->config('logfile'), level => $app->config('loglevel') )); }; $server->run; #EOF
#!@PERL@ #-------------- #--- DAEMON --- #-------------- package PGmaster::Daemon; use strict; use warnings; use POSIX qw(getpid setuid setgid geteuid getegid); use Cwd qw(cwd getcwd chdir); use Mojo::Util qw(dumper); sub new { my $class = shift; my $self = { pid => undef }; bless $self, $class; return $self; } sub fork { my ($self, $user, $group) = shift; my $pid = fork; if ($pid > 0) { exit; } chdir("/"); open(my $stdout, '>&', STDOUT); open(my $stderr, '>&', STDERR); open(STDOUT, '>>', '/dev/null'); open(STDERR, '>>', '/dev/null'); $self->pid(getpid); $self->pid; } sub pid { my ($self, $pid) = @_; return $self->{pid} unless $pid; $self->{pid} = $pid if $pid; $self; } 1; #---------- #--- DB --- #---------- package PGmaster::DB; use strict; use warnings; use DBI; sub new { my ($class, %args) = @_; my $self = { hostname => $args{hostname}, username => $args{username}, password => $args{password}, database => $args{database}, engine => 'Pg', error => '' }; bless $self, $class; return $self; } sub username { my ($self, $username) = @_; return $self->{username} unless $username; $self->{username} = $username; $self; } sub password { my ($self, $password) = @_; return $self->{password} unless $password; $self->{password} = $password; $self; } sub hostname { my ($self, $hostname) = @_; return $self->{hostname} unless $hostname; $self->{hostname} = $hostname; $self; } sub database { my ($self, $database) = @_; return $self->{database} unless $database; $self->{database} = $database; $self; } sub error { my ($self, $error) = @_; return $self->{error} unless $error; $self->{error} = $error; $self; } sub engine { my ($self, $engine) = @_; return $self->{engine} unless $engine; $self->{engine} = $engine; $self; } sub exec { my ($self, $query) = @_; return undef unless $query; my $dsn = 'dbi:'.$self->engine. ':dbname='.$self->database. ';host='.$self->hostname; my $dbi; eval { $dbi = DBI->connect($dsn, $self->username, $self->password, { RaiseError => 1, PrintError => 0, AutoCommit => 1 }); }; $self->error($@); return undef if $@; my $sth; eval { $sth = $dbi->prepare($query); }; $self->error($@); return undef if $@; my $rows = $sth->execute; my @list; while (my $row = $sth->fetchrow_hashref) { push @list, $row; } $sth->finish; $dbi->disconnect; \@list; } sub exec1 { my ($self, $query) = @_; return undef unless $query; my $dsn = 'dbi:'.$self->engine. ':dbname='.$self->database. ';host='.$self->hostname; my $dbi; eval { $dbi = DBI->connect($dsn, $self->username, $self->password, { RaiseError => 1, PrintError => 0, AutoCommit => 1 }); }; $self->error($@); return undef if $@; my $sth; eval { $sth = $dbi->prepare($query); }; $self->error($@); return undef if $@; my $rows = $sth->execute; my $row = $sth->fetchrow_hashref; $sth->finish; $dbi->disconnect; $row; } sub do { my ($self, $query) = @_; return undef unless $query; my $dsn = 'dbi:'.$self->engine. ':dbname='.$self->database. ';host='.$self->hostname; my $dbi; eval { $dbi = DBI->connect($dsn, $self->username, $self->password, { RaiseError => 1, PrintError => 0, AutoCommit => 1 }); }; $self->error($@); return undef if $@; my $rows; eval { $rows = $dbi->do($query) or return undef; }; $self->error($@); return undef if $@; $dbi->disconnect; $rows*1; } 1; #------------- #--- MODEL --- #------------- 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, $db) = @_; my $self = { app => $app, db => $db }; bless $self, $class; return $self; } sub db { return shift->{db}; } sub app { return shift->{app}; } sub hostname_alive { my ($self, $hostname) = @_; return undef unless gethostbyname($hostname); return 1; } sub parse_label { 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-Z0-9\+]{2,4})--([_\-a-zA-Z0-9\.]{1,64})/g; my %data; $data{'dbname'} = $dbname; $data{'timestamp'} = "$Y-$M-$D $h:$m:$s $tz"; $data{'datetime'} = "$Y-$M-$D $h:$m:$s"; $data{'tz'} = "$tz"; $data{'source'} = $host; return \%data; } sub timestamp { my ($self, $timenum) = shift; $timenum = time unless $timenum; return strftime("%Y-%m-%d %H:%M:%S %Z", localtime($timenum)); } sub curr_sec { my $self = shift; return strftime("%S", localtime(time)); } sub curr_time { my $self = shift; my $time = time; my %time; $time{sec} = strftime("%S", localtime($time)); $time{min} = strftime("%M", localtime($time)); $time{hour} = strftime("%H", localtime($time)); $time{mday} = strftime("%d", localtime($time)); $time{month} = strftime("%m", localtime($time)); $time{yarth} = strftime("%Y", localtime($time)); $time{wday} = strftime("%u", localtime($time)); return \%time; } sub size_hr { 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 size_wp { 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 size_m { my ($self, $size) = @_; return int($size/(1024*1024)+0.5); } sub strip_sec { my ($self, $s) = @_; my $a = substr $s, 0, 16; $a =~ s/\//-/g; return $a; } #------------------- #--- AGENT MODEL --- #------------------- sub agent_list { my ($self, $id) = @_; my $where = ''; $where = "where id = $id" if $id; my $query = "select * from agent $where order by hostname"; $self->db->exec($query); } sub agent_exist { my ($self, $hostname) = @_; return undef unless $hostname; my $query = "select id from agent where hostname = '$hostname' limit 1"; my $row = $self->db->exec1($query); my $id = $row->{'id'}; return undef unless $id; $id; } sub agent_hostname { my ($self, $id) = @_; return undef unless $id; my $query = "select hostname from agent where id = $id limit 1"; my $row = $self->db->exec1($query); my $hostname = $row->{'hostname'}; return undef unless $hostname; $hostname; } sub agent_profile { my ($self, $id) = @_; return undef unless $id; my $query = "select * from agent where id = $id limit 1"; my $row = $self->db->exec1($query); return undef unless $row; $row; } sub agent_info { my ($self, $id) = @_; return undef unless $id; my $query = "select sum(d.size) as sum , count(d.name) as count from agent a, db d where a.id = d.agentid and a.id = $id limit 1"; my $row = $self->db->exec1($query); $row; } sub agent_next_id { my $self = shift; my $query = "select id from agent order by id desc limit 1"; my $row = $self->db->exec1($query); my $id = $row->{'id'} || 0; $id += 1; } sub agent_add { my ($self, $hostname, $username, $password) = @_; return undef unless $hostname; return undef unless $username; return undef unless $password; my $id = $self->agent_next_id; my $query = "insert into agent (id, hostname, username, password) values ($id, '$hostname', '$username', '$password')"; $self->db->do($query); $self->agent_exist($hostname); } sub agent_delete { my ($self, $id) = @_; return undef unless $id; my $query = "delete from agent where id = $id"; $self->db->do($query); } sub agent_config { my ($self, $id, $hostname, $username, $password) = @_; my $query = "update agent set hostname = '$hostname', username = '$username', password = '$password' where id = $id"; $self->db->do($query); } sub agent_alive { my ($self, $id) = @_; return undef unless $id; my $hostname = $self->agent_hostname($id); return undef unless $hostname; my $ua = Mojo::UserAgent->new; $ua = $ua->connect_timeout(5); my $tx = $ua->get("https://$hostname:3001/hello"); my $body; eval { $body = $tx->result->body; }; do { $self->app->log->info("agent_alive: $@"); return undef; } if $@; return undef unless $body; my $res = decode_json $body; return 1 if $res->{'message'} eq 'hello'; return undef; } sub agent_db_list { my ($self, $id) = @_; return undef unless $id; my $query = "select * from db where agentid = $id order by name"; $self->db->exec($query); } sub agent_db_update { my ($self, $id) = @_; return undef unless $id; my $agent_profile = $self->agent_profile($id) or return undef; my $hostname = $agent_profile->{'hostname'} || 'undef'; my $username = $agent_profile->{'username'} || 'undef'; my $password = $agent_profile->{'password'} || 'password'; my $ua = Mojo::UserAgent->new(max_redirects => 2) or return undef; $ua = $ua->connect_timeout(5); my $tx = $ua->get("https://$username:$password\@$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 $query = "delete from db where agentid = $id"; $self->db->do($query); foreach my $rec (@{$dblist}) { my $dbname = $rec->{'name'} || 'undef'; my $dbsize = $rec->{'size'} || 1; my $dbowner = $rec->{'owner'} || 'undef'; my $numbackends = $rec->{'numbackends'} || 0; my $query = "insert into db (agentid, name, size, owner, type, numbackends) values ($id, '$dbname', $dbsize, '$dbowner', 'pgsql', $numbackends)"; my $rows = $self->db->do($query); } $dblist; } #------------------- #--- STORE MODEL --- #------------------- sub store_hostname { my ($self, $id) = @_; return undef unless $id; my $query = "select hostname from store where id = $id limit 1"; my $row = $self->db->exec1($query); my $hostname = $row->{'hostname'}; return undef unless $hostname; $hostname; } sub store_profile { my ($self, $id) = @_; return undef unless $id; my $query = "select * from store where id = $id limit 1"; $self->db->exec1($query); } sub store_info { my ($self, $id) = @_; return undef unless $id; my $query = "select sum(d.size) as sum , count(d.name) as count from store s, data d where s.id = d.storeid and s.id = $id limit 1"; my $row = $self->db->exec1($query); return undef unless $row; $row->{'sum'} ||= 0; $row; } sub store_alive { my ($self, $id) = @_; return undef unless $id; my $hostname = $self->store_hostname($id); return undef unless $hostname; my $ua = Mojo::UserAgent->new; $ua = $ua->connect_timeout(5); my $tx = $ua->get("https://$hostname:3002/hello"); my $body; eval { $body = $tx->result->body; }; do { $self->app->log->info("store_alive: $@"); return undef; } if $@; return undef unless $body; my $res = decode_json $body; return 1 if $res->{'message'} eq 'hello'; return undef; } sub store_list { my ($self, $id) = @_; my $where = ''; $where = "where id = '$id'" if $id; my $query = "select * from store $where order by hostname"; $self->db->exec($query); } sub store_exist { my ($self, $hostname) = @_; return undef unless $hostname; my $query = "select id from store where hostname = '$hostname' limit 1"; my $row = $self->db->exec1($query); my $id = $row->{'id'}; return undef unless $id; $id; } sub store_next_id { my $self = shift; my $query = "select id from store order by id desc limit 1"; my $row = $self->db->exec1($query); my $id = $row->{'id'} || 0; $id += 1; } sub store_add { my ($self, $hostname, $username, $password) = @_; return undef unless $hostname; return undef unless $username; return undef unless $password; my $id = $self->store_next_id; my $query = "insert into store (id, hostname, username, password) values ($id, '$hostname', '$username', '$password')"; $self->db->do($query); $self->store_exist($hostname); } sub store_delete { my ($self, $id) = @_; return undef unless $id; my $query = "delete from store where id = $id"; $self->db->do($query); } sub store_config { my ($self, $id, $hostname, $username, $password) = @_; my $query = "update store set hostname = '$hostname', username = '$username', password = '$password' where id = $id"; $self->db->do($query); } sub store_data_list { my ($self, $id) = @_; return undef unless $id; my $query = "select * from data where storeid = $id order by name"; $self->db->exec($query); } sub store_data_update { my ($self, $id) = @_; return undef unless $id; my $store_profile = $self->store_profile($id) or return undef; my $hostname = $store_profile->{'hostname'} || 'undef'; my $username = $store_profile->{'username'} || 'undef'; my $password = $store_profile->{'password'} || 'password'; my $ua = Mojo::UserAgent->new(max_redirects => 2) or return undef; $ua = $ua->connect_timeout(5); my $tx = $ua->get("https://$username:$password\@$hostname:3002/data/list"); my $res = undef; eval { $res = decode_json($tx->result->body); }; return undef if $@; my $free = $res->{'free'} || 0; my $datalist = $res->{'datalist'} || undef; return undef unless $datalist; my $query = "delete from data where storeid = $id"; $self->db->do($query); $query = "update store set free = $free where id = $id"; $self->db->do($query); 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->parse_label($dataname); my $dbname = $label->{'dbname'} || 'undef'; my $source = $label->{'source'} || 'undef'; my $stamp = $label->{'timestamp'} || '1970-01-01 00:00:00 UTC'; my $datetime = $label->{'datetime'} || '1970-01-01 00:00:00'; my $tz = $label->{'tz'} || 'UND'; my $query = "insert into data (storeid, name, size, mtime, type, dbname, source, stamp, datetime, tz) values ($id, '$dataname', $datasize, '$datamtime', 'pgsql', '$dbname', '$source', '$stamp', '$datetime', '$tz')"; $self->db->do($query); push @list, $rec; } return \@list; } sub store_free { my ($self, $id) = @_; return undef unless $id; my $query = "select free from store where id = $id limit 1"; my $row = $self->db->exec1($query); my $free = $row->{'free'}; return undef unless $free; $free; } #--------------- #--- XXXXXXX --- #--------------- sub data_list { 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; } #----------------- #--- JOB MODEL --- #----------------- sub job_create { my ($self, @args) = @_; my $id = $self->job_next_id; 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 $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')"; $self->db->do($query); $id; } sub job_list { my $self = shift; 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"; $self->db->exec($query); } sub job_next_id { my $self = shift; my $query = "select id from job order by id desc limit 1"; my $row = $self->db->exec1($query); my $id = $row->{'id'} || 0; $id += 1; } sub job_exist { my ($self, $id) = @_; my $query = "select id from job where id = $id limit 1"; my $row = $self->db->exec1($query); $id = $row->{'id'}; return undef unless $id; $id; } sub job_update { my ($self, $id, %args) = @_; return undef unless $id; return undef unless $self->job_exist($id); my $job_profile = $self->job_profile($id); my $args = \%args; my $begin = $args->{'begin'} || $job_profile->{'begin'}; my $stop = $args->{'stop'} || $job_profile->{'stop'}; my $type = $args->{'type'} || $job_profile->{'type'}; my $author = $args->{'author'} || $job_profile->{'author'}; my $sourceid = $args->{'sourceid'} || $job_profile->{'sourceid'}; my $destid = $args->{'destid'} || $job_profile->{'destid'}; my $status = $args->{'status'} || $job_profile->{'status'}; my $error = $args->{'error'} || $job_profile->{'error'}; my $message = $args->{'message'} || $job_profile->{'message'}; 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 = $self->db->do($query); $id; } sub job_delete { my ($self, $id) = @_; my $query = "delete from job where id = $id"; $self->db->do($query); $id; } sub job_profile { my ($self, $id) = @_; 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 $row = $self->db->exec1($query); return undef unless $row; $row; } sub job_cb { my ($self, $req) = @_; my $job_id = $req->param('jobid'); my $magic = $req->param('magic'); return undef unless $job_id; return undef unless $magic; my $job_profile = $self->job_profile($job_id); my $error = $req->param('error'); my $status = $req->param('status'); my $message = $req->param('message'); $self->job_update($job_id, error => $error, stop => $self->timestamp) if $error; $self->job_update($job_id, status => $status, stop => $self->timestamp) if $status; $self->job_update($job_id, message => $message, stop => $self->timestamp) if $message; 1; } #---------------------- #--- SCHEDULE MODEL --- #---------------------- sub schedule_list { my ($self, $id) = @_; my $where = ''; $where = "where id = $id" if $id; my $query = "select * from schedule $where order by id"; $self->db->exec($query); } sub schedule_profile { my ($self, $id) = @_; return undef unless $id; my $query = "select * from schedule where id = $id limit 1"; $self->db->exec1($query); } sub schedule_next_id { my $self = shift; my $query = "select id from schedule order by id desc limit 1"; my $row = $self->db->exec1($query); my $id = $row->{'id'}; $id++; $id; } sub schedule_add { my ($self, $type, $source_id, $dest_id, $subject, $mday, $wday, $hour, $min) = @_; return undef unless $type; return undef unless $source_id; return undef unless $dest_id; return undef unless $subject; return undef unless $mday; return undef unless $wday; return undef unless $hour; return undef unless $min; my $id = $self->schedule_next_id; return undef unless $id; my $dbi = DBI->connect($self->dsn, $self->dbuser, $self->dbpasswd) or return undef; my $query = "insert into schedule (id, type, sourceid, destid, subject, mday, wday, hour, min) values ($id, '$type', $source_id, $dest_id, '$subject', '$mday', '$wday', '$hour', '$min')"; my $rows = $dbi->do($query) or return undef; $dbi->disconnect; return $id if $rows*1 > 0; return undef; } sub period_expand { my ($self, $def, $limit, $start) = @_; $limit = 100 unless defined $limit; $start = 1 unless defined $start; my @list = split ',', $def; my @out; my %n; foreach my $sub (@list) { if (my ($num) = $sub =~ m/^(\d+)$/ ) { next if $num < $start; next if $num > $limit; push @out, $num unless $n{$num}; $n{$num} = 1; } elsif (my ($begin, $end) = $sub =~ /^(\d+)[-. ]+(\d+)$/) { foreach my $num ($begin..$end) { next if $num < $start; next if $num > $limit; push @out, $num unless $n{$num}; $n{$num} = 1; } } elsif (my ($inc) = $sub =~ /^[*]\/(\d+)$/) { my $num = $start; while ($num <= $limit) { next if $num < $start; push @out, $num unless $n{$num}; $num += $inc; } } elsif ($sub =~ /^[*]$/) { my $num = $start; my $inc = 1; while ($num <= $limit) { next if $num < $start; next if $num > $limit; push @out, $num unless $n{$num}; $num += $inc; } } } @out = sort { $a <=> $b } @out; return \@out } sub period_compact { my ($self, $list) = @_; my %n; my $out; foreach my $num (@{$list}) { $n{$num} = 1; } foreach my $num (sort { $a <=> $b } (keys %n)) { $out .= $num."-" if $n{$num+1} && not $n{$num-1}; $out .= $num."," unless $n{$num+1}; } $out =~ s/,$//; return $out; } sub period_match { my ($self, $num, $list) = @_; foreach my $elem (@{$list}) { return 1 if $num == $elem; } return undef; } #----------------- #--- MODEL END --- #----------------- 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); use Apache::Htpasswd; sub hello { my $self = shift; $self->render(template => 'hello'); } #------------------ #--- AGENT CONT --- #------------------ sub agent_list { my $self = shift; $self->render(template => 'agent_list'); } sub agent_add { my $self = shift; $self->render(template => 'agent_add', req => $self->req); } sub agent_config { my $self = shift; $self->render(template => 'agent_config', req => $self->req); } sub agent_delete { my $self = shift; $self->render(template => 'agent_delete', req => $self->req); } sub agent_db_list { my $self = shift; my $id = $self->req->param('id'); $self->render(template => 'agent_db_list', req => $self->req); } sub agent_db_create { my $self = shift; $self->render(template => 'agent_db_create', req => $self->req); } sub agent_db_drop { my $self = shift; $self->render(template => 'agent_db_drop', req => $self->req); } sub agent_db_rename { my $self = shift; $self->render(template => 'agent_db_rename', req => $self->req); } sub agent_db_copy { my $self = shift; $self->render(template => 'agent_db_copy', req => $self->req); } sub agent_db_dump { my $self = shift; $self->render(template => 'agent_db_dump', req => $self->req); } sub agent_db_restore { my $self = shift; $self->render(template => 'agent_db_restore', req => $self->req); } #------------------ #--- STORE CONT --- #------------------ sub store_list { my $self = shift; $self->render(template => 'store_list'); } sub store_add { my $self = shift; $self->render(template => 'store_add', req => $self->req); } sub store_config { my $self = shift; $self->render(template => 'store_config', req => $self->req); } sub store_delete { my $self = shift; $self->render(template => 'store_delete', req => $self->req); } sub store_data_list { my $self = shift; $self->render(template => 'store_data_list', req => $self->req); } sub store_data_delete { my $self = shift; $self->render(template => 'store_data_delete', req => $self->req); } sub store_data_download { my $self = shift; $self->render(template => 'store_data_download', req => $self->req); } #------------- #--- XXXXX --- #------------- sub data_list { my $self = shift; $self->render(template => 'data_list', req => $self->req); } #---------------- #--- JOB CONT --- #---------------- sub job_list { my $self = shift; $self->render(template => 'job_list', req => $self->req); } sub job_cb { my $self = shift; $self->app->model->job_cb($self->req); $self->render(json => { responce => 'success' } ); } #--------------------- #--- SCHEDULE CONT --- #--------------------- sub schedule_list { my $self = shift; $self->render(template => 'schedule_list', req => $self->req); } sub schedule_add { my $self = shift; $self->render(template => 'schedule_add', req => $self->req); } sub schedule_delete { my $self = shift; $self->render(template => 'schedule_delete', req => $self->req); } sub schedule_config { my $self = shift; $self->render(template => 'schedule_config', req => $self->req); } #-------------------- #--- SESSION CONT --- #-------------------- sub pwfile { my ($self, $pwdfile) = @_; return $self->app->config('pwdfile') unless $pwdfile; $self->app->config(pwfile => $pwdfile); } sub ucheck { my ($self, $username, $password) = @_; return undef unless $password; return undef unless $username; my $pwdfile = $self->pwfile or return undef; my $res = undef; eval { my $ht = Apache::Htpasswd->new({ passwdFile => $pwdfile, ReadOnly => 1 }); $res = $ht->htCheckPassword($username, $password); }; $res; } sub login { my $self = shift; return $self->redirect_to('/') if $self->session('username'); my $username = $self->req->param('username') || undef; my $password = $self->req->param('password') || undef; return $self->render(template => 'login') unless $username and $password; if ($self->ucheck($username, $password)) { $self->session(username => $username); return $self->redirect_to('/'); } $self->render(template => 'login'); } sub logout { my $self = shift; $self->session(expires => 1); $self->redirect_to('/'); } #---------------- #--- CONT END --- #---------------- 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 POSIX qw(setuid setgid tzset tzname strftime); 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); use File::Basename qw(basename dirname); use Apache::Htpasswd; use Cwd qw(getcwd abs_path); my $appname = 'pgmaster'; #-------------- #--- GETOPT --- #-------------- getopt 'h|help' => \my $help, 'c|config=s' => \my $conffile, 'f|nofork' => \my $nofork, 'u|user=s' => \my $user, 'g|group=s' => \my $group; if ($help) { print qq( Usage: app [OPTIONS] Options -h | --help This help -c | --config=path Path to config file -u | --user=user System owner of process -g | --group=group System group -f | --nofork Dont fork process The options override options from configuration file )."\n"; exit 0; } #------------------ #--- APP CONFIG --- #------------------ my $server = Mojo::Server::Prefork->new; my $app = $server->build_app('PGmaster'); $app = $app->controller_class('PGmaster::Controller'); $app->secrets(['6d578e43ba88260e0375a1a35fd7954b']); $app->static->paths(['@APP_LIBDIR@/public']); $app->renderer->paths(['@APP_LIBDIR@/templs']); $app->config(conffile => $conffile || '@APP_CONFDIR@/pgmaster.conf'); $app->config(pwdfile => '@APP_CONFDIR@/pgmaster.pw'); $app->config(logfile => '@APP_LOGDIR@/pgmaster.log'); $app->config(loglevel => 'info'); $app->config(pidfile => '@APP_RUNDIR@/pgmaster.pid'); $app->config(crtfile => '@APP_CONFDIR@/pgmaster.crt'); $app->config(keyfile => '@APP_CONFDIR@/pgmaster.key'); $app->config(user => $user || '@APP_USER@'); $app->config(group => $group || '@APP_GROUP@'); $app->config(listenaddr4 => '0.0.0.0'); $app->config(listenaddr6 => '[::]'); $app->config(listenport => '3003'); $app->config(tmpdir => '/tmp'); $app->config(dbhost => '127.0.0.1'); $app->config(dbuser => 'postgres'); $app->config(dbpwd => 'password'); $app->config(dbname => 'pgdumper'); if (-r $app->config('conffile')) { $app->log->debug("Load configuration from ".$app->config('conffile')); $app->plugin('JSONConfig', { file => $app->config('conffile') }); } #--------------- #--- HELPERS --- #--------------- $app->helper('reply.not_found' => sub { my $c = shift; return $c->redirect_to('/login') unless $c->session('username'); $c->render(template => 'not_found.production'); }); $app->helper( model => sub { my $db = PGmaster::DB->new( hostname => $app->config("dbhost"), username => $app->config("dbuser"), password => $app->config("dbpwd"), database => $app->config("dbname"), ); state $model = PGmaster::Model->new($app, $db); } ); #-------------- #--- ROUTES --- #-------------- my $r = $app->routes; $r->add_condition( auth => sub { my ($route, $c) = @_; $c->session('username'); } ); $r->any('/login')->to('controller#login'); $r->any('/logout')->to('controller#logout'); $r->any('/hello')->over('auth')->to('controller#hello'); # agent forms $r->any('/agent/list')->over('auth')->to('controller#agent_list'); # agent handlers $r->any('/agent/add')->over('auth')->to('controller#agent_add'); $r->any('/agent/config')->over('auth')->to('controller#agent_config'); $r->any('/agent/delete')->over('auth')->to('controller#agent_delete'); # agent db forms $r->any('/agent/db/list')->over('auth')->to('controller#agent_db_list'); # agent db handlers $r->any('/agent/db/create')->over('auth')->to('controller#agent_db_create'); $r->any('/agent/db/drop')->over('auth')->to('controller#agent_db_drop'); $r->any('/agent/db/copy')->over('auth')->to('controller#agent_db_copy'); $r->any('/agent/db/rename')->over('auth')->to('controller#agent_db_rename'); $r->any('/agent/db/dump')->over('auth')->to('controller#agent_db_dump'); $r->any('/agent/db/restore')->over('auth')->to('controller#agent_db_restore'); # store forms $r->any('/store/list')->over('auth')->to('controller#store_list'); # store handlers $r->any('/store/add')->over('auth')->to('controller#store_add'); $r->any('/store/config')->over('auth')->to('controller#store_config'); $r->any('/store/delete')->over('auth')->to('controller#store_delete'); # store data manipulation $r->any('/store/data/list')->over('auth')->to('controller#store_data_list'); $r->any('/store/data/delete')->over('auth')->to('controller#store_data_delete'); $r->any('/store/data/download')->over('auth')->to('controller#store_data_download'); # generic data list $r->any('/data/list')->over('auth')->to('controller#data_list'); # generic job list $r->any('/job/list')->over('auth')->to('controller#job_list'); $r->any('/schedule/list')->over('auth')->to('controller#schedule_list'); $r->any('/schedule/add')->over('auth')->to('controller#schedule_add'); $r->any('/schedule/delete')->over('auth')->to('controller#schedule_delete'); $r->any('/schedule/config')->over('auth')->to('controller#schedule_config'); # callback handlers $r->any('/agent/job/cb')->to('controller#job_cb'); $r->any('/store/job/cb')->to('controller#job_cb'); #---------------- #--- LISTENER --- #---------------- my $tls = '?'; $tls .= 'cert='.$app->config('crtfile'); $tls .= '&key='.$app->config('keyfile'); my $listen4; if ($app->config('listenaddr4')) { $listen4 = "https://"; $listen4 .= $app->config('listenaddr4').':'.$app->config('listenport'); $listen4 .= $tls; } my $listen6; if ($app->config('listenaddr6')) { $listen6 = "https://"; $listen6 .= $app->config('listenaddr6').':'.$app->config('listenport'); $listen6 .= $tls; } my @listen; push @listen, $listen4 if $listen4; push @listen, $listen6 if $listen6; $server->listen(\@listen); $server->heartbeat_interval(3); $server->heartbeat_timeout(60); #-------------- #--- DAEMON --- #-------------- unless ($nofork) { my $d = PGmaster::Daemon->new; my $user = $app->config('user'); my $group = $app->config('group'); $d->fork; $app->log(Mojo::Log->new( path => $app->config('logfile'), level => $app->config('loglevel') )); } $server->pid_file($app->config('pidfile')); #--------------- #--- WEB LOG --- #--------------- $app->hook(before_dispatch => sub { my $c = shift; my $remote_address = $c->tx->remote_address; my $method = $c->req->method; my $base = $c->req->url->base->to_string; my $path = $c->req->url->path->to_string; my $loglevel = $c->app->log->level; my $url = $c->req->url->to_abs->to_string; unless ($loglevel eq 'debug') { #$c->app->log->info("$remote_address $method $base$path"); $c->app->log->info("$remote_address $method $url"); } if ($loglevel eq 'debug') { $c->app->log->debug("$remote_address $method $url"); } }); #---------------------- #--- SIGNAL HANDLER --- #---------------------- local $SIG{HUP} = sub { $app->log->info('Catch HUP signal'); $app->log(Mojo::Log->new( path => $app->config('logfile'), level => $app->config('loglevel') )); }; $server->run; #EOF
#!@PERL@ 1; #------------ #--- AUTH --- #------------ package PGstore::BasicAuth; use strict; use warnings; use POSIX qw(getpid setuid setgid geteuid getegid); use Cwd qw(cwd getcwd chdir); use Mojo::Util qw(md5_sum b64_decode dumper); use Apache::Htpasswd; sub new { my ($class, $pwdfile) = @_; my $self = { pwdfile => $pwdfile, errstr => undef }; bless $self, $class; return $self; } sub pwdfile { my ($self, $pwdfile) = @_; return $self->{pwdfile} unless $pwdfile; $self->{pwdfile} = $pwdfile if $pwdfile; $self; } sub auth { my ($self, $authstr) = @_; return undef unless $authstr; my $hash = $self->split($authstr); return undef unless $hash; return undef unless -r $self->{pwdfile}; my $res = undef; eval { my $ht = Apache::Htpasswd->new( { passwdFile => $self->pwdfile, ReadOnly => 1 } ); $res = $ht->htCheckPassword( $hash->{username}, $hash->{password} ); }; return undef if $@; $res; } sub username { my ($self, $authstr) = @_; return undef unless $authstr; my $hash = $self->split($authstr); return undef unless $hash; $hash->{username} if $hash; } sub split { my ($self, $authstr) = @_; return undef unless $authstr; my ($type, $enc) = split /\s+/, $authstr; return undef unless ($type eq 'Basic' && $enc); my ($username, $password) = split /:/, b64_decode($enc); return undef unless ($username && $password); { username => $username, password => $password }; } 1; #-------------- #--- DAEMON --- #-------------- package PGstore::Daemon; use strict; use warnings; use POSIX qw(getpid setuid setgid geteuid getegid); use Cwd qw(cwd getcwd chdir); use Mojo::Util qw(dumper); sub new { my $class = shift; my $self = { pid => undef }; bless $self, $class; return $self; } sub fork { my ($self, $user, $group) = shift; my $pid = fork; if ($pid > 0) { exit; } chdir("/"); open(my $stdout, '>&', STDOUT); open(my $stderr, '>&', STDERR); open(STDOUT, '>>', '/dev/null'); open(STDERR, '>>', '/dev/null'); $self->pid(getpid); $self->pid; } sub pid { my ($self, $pid) = @_; return $self->{pid} unless $pid; $self->{pid} = $pid if $pid; $self; } 1; #------------------- #--- CONTROLLER --- #------------------- package PGstore::Controller; use utf8; use strict; use warnings; use Mojo::Base 'Mojolicious::Controller'; use Mojo::Util qw(md5_sum dumper quote encode url_unescape); use Mojo::JSON qw(encode_json decode_json false true); use File::Basename; 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', success => true }); } sub data_list { my $self = shift; my $datadir = $self->app->config("datadir"); return $self->render(json => { datalist => undef, success => false }) unless -d $datadir; return $self->render(json => { datalist => undef, success => false }) 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, success => true } ); } sub data_get { my $self = shift; my $dataname = $self->req->param('dataname'); return $self->rendered(404) unless $dataname; $dataname = url_unescape($dataname); my $datadir = $self->app->config("datadir"); my $file = "$datadir/$dataname"; return $self->rendered(404) unless -r $file; return $self->rendered(404) unless -f $file; $self->renderFile(filepath => "$file"); } sub data_put { my $self = shift; my $datadir = $self->app->config("datadir"); return $self->render(json => { datalist => undef, success => false }) unless -d $datadir; return $self->render(json => { datalist => undef, success => false }) unless -w $datadir; return $self->render(json => { datalist => undef, success => false }) if $self->req->is_limit_exceeded; my @filenames; my $uploads = $self->req->uploads; foreach my $upload (@{$uploads}) { my $dataname = $upload->filename =~ s/^[ \.]+/_/gr; my $datasize = $upload->size; my $df = df("$datadir", 1); return $self->render(json => { success => false }) if $df->{bfree}+1 < $datasize; my $datafile = "$datadir/$dataname"; $upload->move_to($datafile); my $st = stat($datafile); my $realsize = $st->size; do { unlink $dataname; next; } if $datasize != $realsize; push @filenames, { name => $dataname, size => $datasize, realsize => $realsize, realname => $datafile }; } $self->render(json => { datalist => \@filenames, success => true }); } sub data_free { my $self = shift; my $datadir = $self->app->config('datadir'); return $self->render(json => { success => false }) unless -d $datadir; return $self->render(json => { success => false }) unless -r $datadir; my $df = df($datadir, 1); $self->render(json => { total => $df->{blocks}, free => $df->{bfree}, success => true }); } sub data_delete { my $self = shift; my $datadir = $self->app->config('datadir'); my $dataname = $self->req->param('dataname'); return $self->render(json => { dataname => $dataname, success => false }) unless $dataname; return $self->render(json => { dataname => $dataname, success => false }) unless -d $datadir; return $self->render(json => { dataname => $dataname, success => false }) unless -w $datadir; my $datafile = "$datadir/$dataname"; return $self->render(json => { dataname => $dataname, success => true, size => 0}) unless -f $datafile; my $st = stat($datafile); my $datasize = $st->size; return $self->render(json => { dataname => $dataname, success => true, size => $datasize }) if unlink($datafile); $self->render(json => { dataname => $dataname, success => false, size => $datasize }); } 1; #----------- #--- APP --- #----------- package PGstore; use strict; use warnings; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; } 1; #------------- #------------ #--- MAIN --- #------------ #------------- use strict; use warnings; use POSIX qw(setuid setgid tzset tzname strftime); use Mojo::Server::Prefork; use Mojo::IOLoop::Subprocess; use Mojo::Util qw(md5_sum b64_decode 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 $appname = 'pgdumper'; #-------------- #--- GETOPT --- #-------------- getopt 'h|help' => \my $help, 'c|config=s' => \my $conffile, 'f|nofork' => \my $nofork, 'u|user=s' => \my $user, 'g|group=s' => \my $group; if ($help) { print qq( Usage: app [OPTIONS] Options -h | --help This help -c | --config=path Path to config file -u | --user=user System owner of process -g | --group=group System group -f | --nofork Dont fork process The options override options from configuration file )."\n"; exit 0; } #------------------ #--- APP CONFIG --- #------------------ my $server = Mojo::Server::Prefork->new; my $app = $server->build_app('PGstore'); $app = $app->controller_class('PGstore::Controller'); $app->secrets(['6d578e43ba88260e0375a1a35fd7954b']); $app->static->paths(['@APP_LIBDIR@/public']); $app->renderer->paths(['@APP_LIBDIR@/templs']); $app->config(conffile => $conffile || '@APP_CONFDIR@/pgstore.conf'); $app->config(pwdfile => '@APP_CONFDIR@/pgstore.pw'); $app->config(logfile => '@APP_LOGDIR@/pgstore.log'); $app->config(loglevel => 'info'); $app->config(pidfile => '@APP_RUNDIR@/pgstore.pid'); $app->config(crtfile => '@APP_CONFDIR@/pgstore.crt'); $app->config(keyfile => '@APP_CONFDIR@/pgstore.key'); $app->config(user => $user || '@APP_USER@'); $app->config(group => $group || '@APP_GROUP@'); $app->config(listenaddr4 => '0.0.0.0'); $app->config(listenaddr6 => '[::]'); $app->config(listenport => '3002'); $app->config(datadir => '@PGSTORE_DATADIR@'); if (-r $app->config('conffile')) { $app->log->debug("Load configuration from ".$app->config('conffile')); $app->plugin('JSONConfig', { file => $app->config('conffile') }); } #--------------- #--- HELPERS --- #--------------- $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); }); #-------------- #--- ROUTES --- #-------------- my $r = $app->routes; $r->add_condition( auth => sub { my ($route, $c) = @_; my $log = $c->app->log; my $authstr = $c->req->headers->authorization; my $pwdfile = $c->app->config('pwdfile'); my $a = PGstore::BasicAuth->new($pwdfile); $log->info("Try auth user ". $a->username($authstr)); $a->auth($authstr); } ); $r->any('/hello') ->to('controller#hello'); $r->any('/data/list') ->over('auth') ->to('controller#data_list'); $r->any('/data/get') ->over('auth') ->to('controller#data_get'); $r->any('/data/put') ->over('auth') ->to('controller#data_put'); $r->any('/data/free') ->over('auth') ->to('controller#data_free'); $r->any('/data/delete') ->over('auth') ->to('controller#data_delete'); #---------------- #--- LISTENER --- #---------------- my $tls = '?'; $tls .= 'cert='.$app->config('crtfile'); $tls .= '&key='.$app->config('keyfile'); my $listen4; if ($app->config('listenaddr4')) { $listen4 = "https://"; $listen4 .= $app->config('listenaddr4').':'.$app->config('listenport'); $listen4 .= $tls; } my $listen6; if ($app->config('listenaddr6')) { $listen6 = "https://"; $listen6 .= $app->config('listenaddr6').':'.$app->config('listenport'); $listen6 .= $tls; } my @listen; push @listen, $listen4 if $listen4; push @listen, $listen6 if $listen6; $server->listen(\@listen); $server->heartbeat_interval(3); $server->heartbeat_timeout(60); #-------------- #--- DAEMON --- #-------------- unless ($nofork) { my $d = PGstore::Daemon->new; my $user = $app->config('user'); my $group = $app->config('group'); $d->fork; $app->log(Mojo::Log->new( path => $app->config('logfile'), level => $app->config('loglevel') )); } $server->pid_file($app->config('pidfile')); #--------------- #--- WEB LOG --- #--------------- $app->hook(before_dispatch => sub { my $c = shift; my $remote_address = $c->tx->remote_address; my $method = $c->req->method; my $base = $c->req->url->base->to_string; my $path = $c->req->url->path->to_string; my $loglevel = $c->app->log->level; my $url = $c->req->url->to_abs->to_string; unless ($loglevel eq 'debug') { #$c->app->log->info("$remote_address $method $base$path"); $c->app->log->info("$remote_address $method $url"); } if ($loglevel eq 'debug') { $c->app->log->debug("$remote_address $method $url"); } }); #---------------------- #--- SIGNAL HANDLER --- #---------------------- local $SIG{HUP} = sub { $app->log->info('Catch HUP signal'); $app->log(Mojo::Log->new( path => $app->config('logfile'), level => $app->config('loglevel') )); }; $server->run; #EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % my $hostname = $req->param('hostname'); % my $username = $req->param('username'); % my $password = $req->param('password'); % my $m = $self->app->model; % my $agent_id = $m->agent_exist($hostname); % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'agentadd', % sourceid => $agent_id, % destid => $agent_id, % author => $c->session('username') % ); % if ($agent_id) { <div class="callout alert"> Agent <%= $hostname %> yet exist. </div> % $m->job_update($job_id, status => 'mistake', error => 'agentexist', stop => $m->timestamp); % } % unless ($agent_id) { % my $agent_id = $m->agent_add($hostname, $username, $password); % if ($agent_id) { <div class="callout success"> Agent <%= $hostname %> was added successful with id <%= $agent_id %>. </div> <div class="text-center"> <a class="button" href="/agent/list">Agent List</a> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> <a class="button" href="#" data-open="modal-agent-add">Add yet</a> </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % } % unless ($agent_id) { <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="#" data-open="modal-agent-add">Add yet</a> </div> % $m->job_update($job_id, status => 'error', error => 'agentadderr', stop => $m->timestamp); % } % } %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % my $agent_id = $req->param('id'); % my $hostname = $req->param('hostname'); % my $username = $req->param('username'); % my $password = $req->param('password'); % my $m = $self->app->model; % my $res = $m->agent_config($agent_id, $hostname, $username, $password); % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'agentreconf', % sourceid => $agent_id, % destid => $agent_id, % author => $c->session('username') % ); % if ($res) { <div class="callout success"> Agent <%= $hostname %> was updated successful. </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % } % unless ($res) { <div class="callout alert"> Agent <%= $hostname %> was updated unsuccessful. </div> % $m->job_update($job_id, status => 'error', error => 'reconferror', stop => $m->timestamp); % } <div class="text-center"> <a class="button" href="/agent/list">AgentList</a> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % use Mojo::JSON qw(j encode_json decode_json true false); % my $m = $self->app->model; % my $dbname = $req->param('dbname') || undef; % my $agent_id = $req->param('agentid') || undef; % my $newname = $req->param('newname') || undef; % my $agent_profile = $m->agent_profile($agent_id); % my $agent_hostname = $agent_profile->{'hostname'} || 'undef'; % my $agent_username = $agent_profile->{'username'} || 'undef'; % my $agent_password = $agent_profile->{'password'} || 'undef'; % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'dbcopy', % sourceid => $agent_id, % destid => $agent_id, % author => $c->session('username') % ); % my $jobMagic = $m->job_profile($job_id)->{'magic'}; % my $ua = Mojo::UserAgent->new(max_redirects => 5); % my $param = "dbname=$dbname"; % $param .= "&newname=$newname"; % my $tx = $ua->get("https://$agent_username:$agent_password\@$agent_hostname:3001/db/copy?$param"); % my $body; % eval { $body = $tx->result->body }; % my $res = decode_json($body); % if ($res->{success}) { <div class="callout success"> Agent <%= $agent_hostname %> copy database <%= $dbname %> to <%= $newname %> as job <%= $job_id %>. </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % $m->agent_db_update($agent_id); % } % unless ($res->{success}) { <div class="callout alert"> Agent <%= $agent_hostname %> cannot copy <%= $dbname %>. Job <%= $job_id %> was cancel. </div> % $m->job_update($job_id, status => 'pushjob', error => 'pusherr', stop => $m->timestamp); % } <div class="text-center"> <a class="button" href="/agent/list">AgentList</a> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % use Mojo::JSON qw(encode_json decode_json true false); % my $m = $self->app->model; % my $dbname = $req->param('dbname') || undef; % my $agent_id = $req->param('agentid') || undef; % my $agent_profile = $m->agent_profile($agent_id); % my $agent_hostname = $agent_profile->{'hostname'} || 'undef'; % my $agent_username = $agent_profile->{'username'} || 'undef'; % my $agent_password = $agent_profile->{'password'} || 'undef'; % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'dbcreate', % sourceid => $agent_id, % destid => $agent_id, % author => $c->session('username') % ); % my $jobMagic = $m->job_profile($job_id)->{'magic'}; % my $ua = Mojo::UserAgent->new(max_redirects => 5); % my $param = "dbname=$dbname"; % my $tx = $ua->get("https://$agent_username:$agent_password\@$agent_hostname:3001/db/create?$param"); % my $body; % eval { $body = $tx->result->body }; % my $res = decode_json($body); % if ($res->{success}) { <div class="callout success"> Agent <%= $agent_hostname %> create database <%= $dbname %> as job <%= $job_id %>. </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % $m->agent_db_update($agent_id); % } % unless ($res->{success}) { <div class="callout alert"> Agent <%= $agent_hostname %> cannot create <%= $dbname %>. Job <%= $job_id %> was cancel. </div> % $m->job_update($job_id, status => 'pushjob', error => 'pusherr', stop => $m->timestamp); % } <div class="text-center"> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % use Mojo::JSON qw(encode_json decode_json true false); % my $m = $self->app->model; % my $dbname = $req->param('dbname') || undef; % my $agent_id = $req->param('agentid') || undef; % my $agent_profile = $m->agent_profile($agent_id); % my $agent_hostname = $agent_profile->{'hostname'} || 'undef'; % my $agent_username = $agent_profile->{'username'} || 'undef'; % my $agent_password = $agent_profile->{'password'} || 'undef'; % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'dbdrop', % sourceid => $agent_id, % destid => $agent_id, % author => $c->session('username') % ); % my $jobMagic = $m->job_profile($job_id)->{'magic'}; % my $ua = Mojo::UserAgent->new(max_redirects => 5); % my $param = "dbname=$dbname"; % my $tx = $ua->get("https://$agent_username:$agent_password\@$agent_hostname:3001/db/drop?$param"); % my $body; % eval { $body = $tx->result->body }; % my $res = decode_json($body); % if ($res->{success}) { <div class="callout success"> Agent <%= $agent_hostname %> drop database <%= $dbname %> as job <%= $job_id %>. </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % $m->agent_db_update($agent_id); % } % unless ($res->{success}) { <div class="callout alert"> Agent <%= $agent_hostname %> cannot drop <%= $dbname %>. Job <%= $job_id %> was cancel. </div> % $m->job_update($job_id, status => 'pushjob', error => 'pusherr', stop => $m->timestamp); % $m->agent_db_update($agent_id); % } <div class="text-center"> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % use Mojo::JSON qw(encode_json decode_json true false); % my $m = $self->app->model; % my $dbname = $req->param('dbname') || undef; % my $agent_id = $req->param('agentid') || undef; % my $store_id = $req->param('storeid') || undef; % my $agent_profile = $m->agent_profile($agent_id); % my $agent_hostname = $agent_profile->{'hostname'} || ''; % my $agent_username = $agent_profile->{'username'} || ''; % my $agent_password = $agent_profile->{'password'} || ''; % my $store_profile = $m->store_profile($store_id); % my $store_hostname = $store_profile->{'hostname'} || ''; % my $store_username = $store_profile->{'username'} || ''; % my $store_password = $store_profile->{'password'} || ''; % my $masterHostname = $self->app->config('hostname'); % my $job_id = $m->job_create; % $m->job_update($job_id, type => 'dbdump', sourceid => $agent_id, destid => $store_id, author => $c->session('username')); % my $jobMagic = $m->job_profile($job_id)->{'magic'}; % my $agent_alive = $m->agent_alive($agent_id); % unless ($agent_alive) { <div class="callout alert"> Oops...Agent <%= $agent_hostname %> not respond. Please, check agent configuration. </div> % $m->job_update($job_id, error => 'connecterr', stop => $m->timestamp); % } % my $store_alive = $m->store_alive($store_id); % unless ($store_alive) { <div class="callout alert"> Oops.. Store <%= $store_hostname %> not respond. Please, check store configuration. </div> % $m->job_update($job_id, error => 'connecterr', stop => $m->timestamp); % } % if ($agent_alive && $store_alive) { % my $ua = Mojo::UserAgent->new(max_redirects => 5); % my $param = "dbname=$dbname"; % $param = $param."&store=$store_hostname"; % $param = $param."&storelogin=$store_username"; % $param = $param."&storepwd=$store_password"; % $param = $param."&master=$masterHostname"; % $param = $param."&jobid=$job_id"; % $param = $param."&magic=$jobMagic"; % $m->job_update($job_id, status => 'pushjob'); % my $body = $ua->get("https://$agent_username:$agent_password\@$agent_hostname:3001/db/dump?$param")->result->body; % my $res = decode_json($body); % if ($res->{success}) { <div class="callout success"> Agent <%= $agent_hostname %> now will create dump of database <%= $dbname %> as job <%= $job_id %>. </div> % } % unless ($res->{success}) { <div class="callout alert"> Agent <%= $agent_hostname %> not correct responded for the request. Job <%= $job_id %> was cancel. </div> % $m->job_update($job_id, error => 'pushjoberr', stop => $m->timestamp); % } % } <div class="text-center"> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> <a class="button" href="/store/data/list?id=<%= $store_id %>">DataList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % my $m = $self->app->model; % my $agent_id = $req->param('id'); % my $agent_hostname = $m->agent_hostname($agent_id); % my $agent_alive = $m->agent_alive($agent_id); % unless ($agent_alive) { <div class="callout alert"> Oops...Agent <%= $agent_hostname %> not responded. Use cached database list. </div> % } % if ($agent_alive) { % my $res = $m->agent_db_update($agent_id); % unless ($res) { <div class="callout alert"> Oops...Agent <%= $agent_hostname %> not responded for update db list. Use cached database list. </div> % } % } % my $dbList = $m->agent_db_list($agent_id); % my $store_list = $m->store_list; % 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 %> from <%= $agent_hostname %></h5> </div> <form accept-charset="UTF-8" action="/agent/db/dump" method="get" data-abide novalidate> <input type="hidden" name="agentid" value="<%= $agent_id %>" /> <input type="hidden" name="dbname" value="<%= $dbname %>" /> <label>Store hostname <select name="storeid" id="store-hostname" required> <option value=""></option> % foreach my $store (@{$store_list}) { % my $storeName = $store->{'hostname'}; % my $store_id = $store->{'id'}; <option value="<%= $store_id %>"><%= $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">×</button> </div> %# --- schedule of dump database --- <div class="reveal" id="modal-schedule-<%= $dbname %>" data-reveal> <div class="text-center"> <h5>Add dump schedule <%= $dbname %></h5> </div> <form accept-charset="UTF-8" action="/schedule/add" method="get" data-abide novalidate> <input type="hidden" name="type" value="dump" /> <input type="hidden" name="sourceid" value="<%= $agent_id %>" /> <input type="hidden" name="subject" value="<%= $dbname %>" /> <label>Store hostname <select name="destid" required> <option value=""></option> % foreach my $store (@{$store_list}) { % my $storeName = $store->{'hostname'}; % my $store_id = $store->{'id'}; <option value="<%= $store_id %>"><%= $storeName %></option> % } </select> </label> <label>Day of month <input type="text" name="mday" value="1-31" placeholder="1-31" required pattern="[0-9\-;.,/*]{1,64}"/> <span class="form-error">mandatory, 1 or more letter, like 23,15-18</span> </label> <label>Day of week <input type="text" name="wday" value="1-7" placeholder="1-7" required pattern="[0-9\-,;./*]{1,64}"/> <span class="form-error">mandatory, 1 or more letter</span> </label> <label>Hour <input type="text" name="hour" value="6,23" placeholder="" required pattern="[0-9\-,;./*]{1,64}"/> <span class="form-error">mandatory, 1 or more letter</span> </label> <label>Min <input type="text" name="min" value="10" placeholder="" required pattern="[0-9\-,;./*]{1,64}"/> <span class="form-error">mandatory, 1 or more letter</span> </label> <p class="text-center"> <button type="submit" class="button success">Accept</button> <button class="button" data-close="modal-schedule-<%= $dbname %>" type="button">Escape</button> </p> </form> <button class="close-button" data-close="modal-schedule-<%= $dbname %>" type="button">×</button> </div> %# --- drop database --- <div class="reveal" id="modal-drop-<%= $dbname %>" data-reveal> <div class="text-center"> <h5>Drop database <%= $dbname %> on <%= $agent_hostname %> </h5> </div> <form accept-charset="UTF-8" action="/agent/db/drop" method="get" data-abide novalidate> <input type="hidden" name="agentid" value="<%= $agent_id %>" /> <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">×</button> </div> %# --- rename database --- <div class="reveal" id="modal-rename-<%= $dbname %>" data-reveal> <div class="text-center"> <h5>Rename database <%= $dbname %> on <%= $agent_hostname %> </h5> </div> <form accept-charset="UTF-8" action="/agent/db/rename" method="get" data-abide novalidate> <input type="hidden" name="agentid" value="<%= $agent_id %>" /> <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">×</button> </div> %# --- copy database --- <div class="reveal" id="modal-copy-<%= $dbname %>" data-reveal> <div class="text-center"> <h5>Copy database <%= $dbname %> on <%= $agent_hostname %> </h5> </div> <form accept-charset="UTF-8" action="/agent/db/copy" method="get" data-abide novalidate> <input type="hidden" name="agentid" value="<%= $agent_id %>" /> <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">×</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> Database <%= $dbname %> on <%= $agent_hostname %> </h5> <div class="stacked-for-small button-group"> <a class="button" href="#" data-open="modal-schedule-<%= $dbname %>"><i class="fi-plus" style="font-size:1.3em;"></i> ToPlan</a> <a class="button" href="#" data-open="modal-dump-<%= $dbname %>"><i class="fi-archive" style="font-size:1.3em;"></i> Dump</a> <a class="button" href="#" data-open="modal-rename-<%= $dbname %>"><i class="fi-pencil" style="font-size:1.3em;"></i> Rename</a> <a class="button" href="#" data-open="modal-copy-<%= $dbname %>"><i class="fi-page-copy" style="font-size:1.3em;"></i> Copy</a> <a class="button alert" href="#" data-open="modal-drop-<%= $dbname %>"><i class="fi-trash" style="font-size:1.3em;"></i> Drop</a> </div> </div> <button class="close-button" data-close="modal-<%= $dbname %>" type="button">×</button> </div> % $num++; % } <div class="text-center"> <h5> Agent <%= $agent_hostname%> database list <a href="/agent/db/list?id=<%= $agent_id %>"><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>conn</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'}; % my $numbackends = $db->{'numbackends'}; % $numbackends = '' if ($numbackends == 0); <tr> <td><%= $num %></td> <td><a href="#" data-open="modal-<%= $dbname %>"><%= $dbname %></a></td> <td><%= $dbsize %></td> <td><%= $numbackends %></td> <td><%= $dbowner %></td> </tr> % $num++; % } </tbody> </table> <script> $.extend(true, $.fn.dataTable.defaults, { "searching": true, "ordering": true, "language": { "search": "", "lengthMenu": "_MENU_", }, } ); $(document).ready(function() { $('#table').DataTable(); }); </script> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % use Mojo::JSON qw(encode_json decode_json true false); % my $m = $self->app->model; % my $dbname = $req->param('dbname') || undef; % my $agent_id = $req->param('agentid') || undef; % my $newname = $req->param('newname') || undef; % my $agent_profile = $m->agent_profile($agent_id); % my $agent_hostname = $agent_profile->{'hostname'} || ''; % my $agent_username = $agent_profile->{'username'} || 'undef'; % my $agent_password = $agent_profile->{'password'} || 'undef'; % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'dbrename', % sourceid => $agent_id, % destid => $agent_id % ); % my $jobMagic = $m->job_profile($job_id)->{'magic'}; % my $ua = Mojo::UserAgent->new(max_redirects => 5); % my $param = "dbname=$dbname"; % $param .= "&newname=$newname"; % my $tx = $ua->get("https://$agent_username:$agent_password\@$agent_hostname:3001/db/rename?$param"); % my $body; % eval { $body = $tx->result->body }; % my $res = decode_json($body); % if ($res->{success}) { <div class="callout success"> Agent <%= $agent_hostname %> rename database <%= $dbname %> to <%= $newname %> as job <%= $job_id %>. </div> % $m->agent_db_update($agent_id); % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % } % unless ($res->{success}) { <div class="callout alert"> Agent <%= $agent_hostname %> cannot rename <%= $dbname %>. Job <%= $job_id %> was cancel. </div> % $m->agent_db_update($agent_id); % $m->job_update($job_id, status => 'pushjob', error => 'pusherr', stop => $m->timestamp); % } <div class="text-center"> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % use Mojo::JSON qw(encode_json decode_json true false); % my $m = $self->app->model; % my $dbname = $req->param('dbname') || undef; % my $dataname = $req->param('dataname') || undef; % my $agent_id = $req->param('agentid') || undef; % my $store_id = $req->param('storeid') || undef; % my $newname = $req->param('newname') || undef; % my $agent_profile = $m->agent_profile($agent_id); % my $agent_hostname = $agent_profile->{'hostname'} || ''; % my $agent_username = $agent_profile->{'username'} || ''; % my $agent_password = $agent_profile->{'password'} || ''; % my $store_profile = $m->store_profile($store_id); % my $store_hostname = $store_profile->{'hostname'} || ''; % my $store_username = $store_profile->{'username'} || ''; % my $store_password = $store_profile->{'password'} || ''; % my $masterHostname = $self->app->config('hostname'); % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'dbrestore', % sourceid => $store_id, % destid => $agent_id % ); % my $jobMagic = $m->job_profile($job_id)->{'magic'}; % my $agent_alive = $m->agent_alive($agent_id); % unless ($agent_alive) { <div class="callout alert"> Oops...Agent <%= $agent_hostname %> not respond. Please, check agent configuration. </div> % $m->job_update($job_id, error => 'connecterr', stop => $m->timestamp); % } % my $store_alive = $m->store_alive($store_id); % unless ($store_alive) { <div class="callout alert"> Oops.. Store <%= $store_hostname %> not respond. Please, check store configuration. </div> % $m->job_update($job_id, error => 'connecterr', stop => $m->timestamp); % } % if ($agent_alive && $store_alive) { % my $ua = Mojo::UserAgent->new(max_redirects => 5); % my $param = "dbname=$dbname"; % $param .= "&dataname=$dataname"; % $param .= "&store=$store_hostname"; % $param .= "&storelogin=$store_username"; % $param .= "&storepwd=$store_password"; % $param .= "&newname=$newname"; % $param .= "&master=$masterHostname"; % $param .= "&jobid=$job_id"; % $param .= "&magic=$jobMagic"; % $m->job_update($job_id, status => 'pushjob'); % my $tx = $ua->get("https://$agent_username:$agent_password\@$agent_hostname:3001/db/restore?$param"); % my $body = '{"result":"mistake"}'; % eval { $body = $tx->result->body }; % my $res = decode_json($body); % if ( $res->{success} ) { <div class="callout success"> Agent <%= $agent_hostname %> now restore database <%= $dbname %> as job <%= $job_id %>. </div> % $m->job_update($job_id, status => 'pushjob', error => 'noerr', stop => $m->timestamp); % } % if ( $res->{success} ) { <div class="callout alert"> Agent <%= $agent_hostname %> not correct responded for the request. Job <%= $job_id %> was cancel. </div> % $m->job_update($job_id, status => 'pushjoberr', error => 'pushjoberr', stop => $m->timestamp); % } % } <div class="text-center"> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> <a class="button" href="/store/data/list?id=<%= $store_id %>">DataList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % my $m = $self->app->model; % my $agent_id = $req->param('id'); % my $res = $self->app->model->agent_delete($agent_id); % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'agentdelete', % sourceid => $agent_id, % destid => $agent_id, % author => $c->session('username') % ); % if ($res) { <div class="callout success"> Agent id <%= $agent_id %> was deleted successful. </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % } % unless ($res) { <div class="callout alert"> Agent id <%= $agent_id %> was deleted unsuccessful. </div> % $m->job_update($job_id, status => 'error', error => 'agentdelerr', stop => $m->timestamp); % } <div class="text-center"> <a class="button" href="/agent/list">Agent List</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(b64_encode b64_decode md5_sum dumper); % my $m = $self->app->model; % my $agent_list = $m->agent_list; % foreach my $agent (@{$agent_list}) { % my $agent_id = $agent->{'id'}; % my $hostname = $agent->{'hostname'}; % my $username = $agent->{'username'}; % my $password = $agent->{'password'}; <div class="reveal" id="modal-agent-config-<%= $agent_id %>" data-reveal> <form accept-charset="UTF-8" action="/agent/config" method="get" data-abide novalidate> <h5>Configuration agent <%= $hostname %></h5> <input type="hidden" name="id" value="<%= $agent_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 success">Accept</button> </p> </form> <button class="close-button" data-close="modal-agent-config-<%= $agent_id %>" type="button">×</button> </div> <div class="reveal" id="modal-agent-delete-<%= $agent_id %>" data-reveal> <div class="text-center"> <h5>Do you want to delete agent <%= $hostname %>?</h5> <a class="button" data-close="modal-agent-delete-<%= $agent_id %>">Escape</a> <a class="button alert" href="/agent/delete?id=<%= $agent_id %>">Delete</a> </div> <button class="close-button" data-close="modal-agent-delete-<%= $agent_id %>" type="button">×</button> </div> <div class="reveal" id="modal-agent-db-create-<%= $agent_id %>" data-reveal> <h5>Create new database on agent [<%= $hostname %>] </h5> <form accept-charset="UTF-8" action="/agent/db/create" method="get" data-abide novalidate> <input type="hidden" name="agentid" value="<%= $agent_id %>" /> <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-agent-db-create-<%= $agent_id %>" type="button">×</button> </div> <div class="reveal" id="modal-agent-<%= $agent_id %>" data-reveal> <div class="text-center"> <h5>Agent <%= $hostname %></h5> <div class="stacked-for-small button-group"> <a class="button" href="#" data-open="modal-agent-config-<%= $agent_id %>">ReConf</a> <a class="button" href="/agent/db/list?id=<%= $agent_id %>">DBList</a> <a class="button" href="#" data-open="modal-agent-db-create-<%= $agent_id %>">DBCreate</a> <a class="button alert" href="#" data-open="modal-agent-delete-<%= $agent_id %>">Delete</a> </div> </div> <button class="close-button" data-close="modal-agent-<%= $agent_id %>" aria-label="Close modal" type="button"> <span aria-hidden="true">×</span> </button> </div> % } <div class="text-center"> <h5>Agent list <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> <td>count db</td> <td>total</td> </tr> </thead> <tbody> % my $num = 1; % foreach my $agent (@{$agent_list}) { % my $agent_id = $agent->{'id'}; % my $hostname = $agent->{'hostname'}; % my $agent_info = $m->agent_info($agent_id); % my $sum = $agent_info->{'sum'} || 0; % my $count = $agent_info->{'count'} || 0; <tr> <td><%= $num %></td> <td><a href="#" data-open="modal-agent-<%= $agent_id %>"><%= $hostname %></a></td> <td><%= $count %></td> <td><%= $m->size_m($sum) %></td> </tr> % $num++; % } </tbody> </table> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % my $data_list = $self->app->model->data_list; <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 (@{$data_list}) { % my $filename = $data->{'name'}; % my $dbname = $data->{'dbname'}; % my $stamp = $data->{'stamp'}; % my $size = $data->{'size'}; % my $store = $data->{'store'}; % my $store_id = $data->{'storeid'}; % my $source = $data->{'source'}; <tr> <td><%= $num %></td> <td><a href="/store/data/list?id=<%= $store_id %>"><%= $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, "language": { "search": "", "lengthMenu": "_MENU_", }, }); $(document).ready(function() { $('#table').DataTable({ "info": false, "processing": true }); }); </script> %#EOF
%# %# $Id: exception.html.ep 627 2017-04-15 13:02:08Z ziggi $ %# % layout 'default'; % title 'Error'; <h5>Oops... Exception</h5> <pre> %= $exception </pre> %#EOF
%# %# $Id: exception.html.ep 627 2017-04-15 13:02:08Z ziggi $ %# % layout 'default'; % title 'Error'; <h5>Oops... Exception</h5> <pre> %= $exception </pre> %#EOF
% layout 'default'; % title 'Dumper Hello'; % use Mojo::Util qw(b64_encode b64_decode md5_sum dumper); % use Encode qw(decode encode); <h5 class="text-center">Hello, body! How are you there?</h5> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Switch; % use Mojo::Util qw(dumper); % my $m = $self->app->model; % my $job_list = $m->job_list; <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>job#</td> <td>type</td> <td>status</td> <td>err</td> <td>source</td> <td>dest</td> <td>start</td> <td>last</td> <td>author</td> </tr> </thead> <tbody> % my $num = 1; % foreach my $job (@{$job_list}) { % my $job_id = $job->{'id'}; % my $type = $job->{'type'}; % my $source_id = $job->{'sourceid'}; % my $dest_id = $job->{'destid'}; % my $sourcehost = ''; % my $desthost = ''; % my $status = $job->{'status'}; % my $start = $job->{'begin'}; % my $stop = $job->{'stop'}; % my $error = $job->{'error'} || 'noerr'; % my $author = $job->{'author'} || 'unkn'; % if ($type eq 'dbdump') { % $sourcehost = $m->agent_profile($source_id)->{'hostname'} if $source_id; % $desthost = $m->store_profile($dest_id)->{'hostname'} if $dest_id; % } % if ($type eq 'dbcopy' || $type eq 'dbrename' || $type eq 'dbdrop') { % $sourcehost = $m->agent_profile($source_id)->{'hostname'} if $source_id; % $desthost = ''; % } % $error = '' if ($error eq 'noerr' || $error eq 'undef'); % $stop = '' if ($stop =~ /1970-01-01/); <tr> <td><%= $num %></td> <td><%= $job_id %></td> <td><%= $type %></td> <td><%= $status %></td> <td><%= $error %></td> <td><%= $sourcehost %></td> <td><%= $desthost %></td> <td><%= $start %></td> <td><%= $stop %></td> <td><%= $author %></td> </tr> % $num++; % } </tbody> </table> <script> $.extend(true, $.fn.dataTable.defaults, { "searching": true, "ordering": true, "lengthChange": true, "language": { "search": "", "lengthMenu": "_MENU_", }, }); %#$(document).ready(function() { %# $('#table').DataTable({ %# "info": false, %# "processing": true %# }); %#}); </script> %#<script> %#var timeout = setTimeout("location.reload(true);",6000); %#function resetTimeout() { %# clearTimeout(timeout); %# timeout = setTimeout("location.reload(true);",6000); %#} %#</script> %#EOF
%# %# $Id: login.html.ep 634 2017-04-15 13:55:49Z ziggi $ %# <html class="no-js" lang="en" dir="ltr"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login</title> <link rel="stylesheet" href="/css/foundation-float.min.css"> <link rel="stylesheet" href="/css/app.css"> <link rel="stylesheet" href="/icons/foundation-icons.css"> <script src="/js/jquery.min.js"></script> <script src="/js/foundation.min.js"></script> </head> <body> <div class="row"> </div> <div class="row"> <div class="small-3 columns hide-for-small"> </div> <div class="small-6 columns text-center"> <div class="row"> <div class="columns"> <form accept-charset="UTF-8" method="post" action="/login"> <div class="row column"> <h4 class="text-center">Login with your username</h4> <label>_username <input type="text" name="username" placeholder="username" /> </label> <label>_password <input type="password" name="password" placeholder="password" /> </label> <p> <button type="submit" class="button">Log In</button> </p> <p class="text-center"></p> </div> </form> </div> </div> </div> <div class="small-3 columns hide-for-small"> </div> </div> <hr/> <div class="row"> <p class="text-center"><small>Made by <a href="http://wiki.unix7.org">Borodin Oleg</a></small></p> </div> <script src="/js/app.js"></script> </body> </html> <!- EOF -> %# EOF
%# %# $Id: not_found.html.ep 627 2017-04-15 13:02:08Z ziggi $ %# % layout 'default'; % title 'Dumper'; <h5>404 Page not found</h5> %#EOF
%# %# $Id: not_found.html.ep 627 2017-04-15 13:02:08Z ziggi $ %# % layout 'default'; % title 'Dumper'; <h5>404 Page not found</h5> %#EOF
%# %# $Id$ %# % use Mojo::Util qw(dumper); % layout 'default'; % title 'Dumper'; % my $type = $req->param('type'); % my $source_id = $req->param('sourceid'); % my $dest_id = $req->param('destid'); % my $subject = $req->param('subject'); % my $mday = $req->param('mday'); % my $wday = $req->param('wday'); % my $hour = $req->param('hour'); % my $min = $req->param('min'); % my $m = $self->app->model; % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'scheduleadd', % sourceid => $source_id, % destid => $dest_id, % author => $c->session('username') % ); % my $schedule_id = $m->schedule_add($type, $source_id, $dest_id, $subject, $mday, $wday, $hour, $min); % if ($schedule_id) { <div class="callout success"> Schedule record was added successful with id <%= $schedule_id %>. </div> $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % } % unless($schedule_id) { <div class="callout alert"> Schedule record was added unsuccessful =( </div> $m->job_update($job_id, status => 'error', error => 'shcheduleadderr', stop => $m->timestamp); % } %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % my $m = $self->app->model; % my $schedule_list = $m->schedule_list; <div class="text-center"> <h5> Schedule list <a href="/schedule/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>source</td> <td>dest</td> <td>subj</td> <td>mday</td> <td>wday</td> <td>hour</td> <td>min</td> </tr> </thead> <tbody> % my $num = 1; % foreach my $descr (@{$schedule_list}) { % my $type = $descr->{'type'}; % my $source_id = $descr->{'sourceid'}; % my $dest_id = $descr->{'destid'}; % my $subject = $descr->{'subject'}; % my $mday = $descr->{'mday'}; % my $wday = $descr->{'wday'}; % my $hour = $descr->{'hour'}; % my $min = $descr->{'min'}; % my $source = 'undef'; % my $dest = 'undef'; % if ($type eq 'dump') { % $source = $m->agent_hostname($source_id) || 'undef'; % $dest = $m->store_hostname($dest_id) || 'undef'; % } <tr> <td><%= $num %></td> <td><%= $type %></td> <td><%= $source %></td> <td><%= $dest %></td> <td><%= $subject %></td> <td><%= $mday %></td> <td><%= $wday %></td> <td><%= $hour %></td> <td><%= $min %></td> </tr> % $num++; % } </tbody> </table> <script> $.extend(true, $.fn.dataTable.defaults, { "searching": true, "ordering": true, "lengthChange": true, "language": { "search": "", "lengthMenu": "_MENU_", }, }); $(document).ready(function() { $('#table').DataTable({ "info": false, "processing": true }); }); </script> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % my $hostname = $req->param('hostname'); % my $username = $req->param('username'); % my $password = $req->param('password'); % my $m = $self->app->model; % my $store_id = $m->store_exist($hostname); % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'storeadd', % sourceid => $store_id, % destid => $store_id, % author => $c->session('username') % ); % if ($store_id) { <div class="callout alert"> Store <%= $hostname %> yet exist. </div> % $m->job_update($job_id, status => 'mistake', error => 'storeexist', stop => $m->timestamp); % } % unless ($store_id) { % my $store_id = $self->app->model->store_add($hostname, $username, $password); % if ($store_id) { <div class="callout success"> Store <%= $hostname %> was added successful with id <%= $store_id %>. </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % } % unless ($store_id) { <div class="callout alert"> Store <%= $hostname %> was added unsuccessful =( </div> % $m->job_update($job_id, status => 'error', error => 'storeadderr', stop => $m->timestamp); % } % } <div class="text-center"> <a class="button" href="/store/list">StoreList</a> <a class="button" href="/store/data/list?id=<%= $store_id %>">DataList</a> <a class="button" href="#" data-open="modal-store-add">Add yet</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % my $m = $self->app->model; % my $store_id = $req->param('id'); % my $hostname = $req->param('hostname'); % my $username = $req->param('username'); % my $password = $req->param('password'); % my $res = $m->store_config($store_id, $hostname, $username, $password); % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'storereconf', % sourceid => $store_id, % destid => $store_id, % author => $c->session('username') % ); % if ($res) { <div class="callout success"> Store <%= $hostname %> was updated successful. </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % } % unless ($res) { <div class="callout alert"> Store <%= $hostname %> was updated unsuccessful. </div> % $m->job_update($job_id, status => 'error', error => 'reconferror', stop => $m->timestamp); % } <div class="text-center"> <a class="button" href="/store/list">StoreList</a> <a class="button" href="/store/data/list?id=<%= $store_id %>">DataList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper url_escape); % use Mojo::JSON qw(decode_json); % my $m = $self->app->model; % my $store_id = $req->param('id'); % my $dataname = $req->param('dataname'); % my $store_profile = $m->store_profile($store_id); % my $store_hostname = $store_profile->{'hostname'}; % my $store_username = $store_profile->{'username'}; % my $store_password = $store_profile->{'password'}; % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'dropdata', % sourceid => $store_id, % destid => $store_id, % author => $c->session('username') % ); % my $ua = Mojo::UserAgent->new(max_redirects => 5); % my $dataname_enc = url_escape $dataname; % my $param = "dataname=$dataname_enc"; % my $tx = $ua->get("https://$store_username:$store_password\@$store_hostname:3002/data/delete?$param"); % my $body; % eval { $body = $tx->result->body }; % my $res = decode_json($body); % if ($res->{success}) { <div class="callout success"> Store <%= $store_hostname %> deleted <%= $dataname %> as job <%= $job_id %>. </div> % $m->store_data_update($store_id); % $m->job_update($job_id, status => 'deleted', error => 'noerr', stop => $m->timestamp); % } % unless ($res->{success}) { <div class="callout alert"> Store <%= $store_hostname %> cannot delete <%= $dataname %>. Job <%= $job_id %> was cancel. </div> % $m->job_update($job_id, status => 'pushjob', error => 'pusherr', stop => $m->timestamp); % } <div class="text-center"> <a class="button" href="/store/list">StoreList</a> <a class="button" href="/store/data/list?id=<%= $store_id %>">DataList</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper md5_sum url_escape); % sub _stripDate { % my $a = substr shift, 0, 16; % $a =~ s/\//-/g; % return $a; % } % my $m = $self->app->model; % my $store_id = $req->param('id'); % my $store_hostname = $m->store_hostname($store_id); % my $store_alive = $m->store_alive($store_id); % unless ($store_alive) { <div class="callout alert"> Oops.. Store <%= $store_hostname %> not respond. Please, check store configuration. Now will use cached dataset list. </div> % } % if ($store_alive) { % my $res = $m->store_data_update($store_id); % unless ($res) { <div class="callout alert"> Oops...Store <%= $store_hostname %> not responded for update data list. Use cached data list. </div> % } % } % my $data_list = $m->store_data_list($store_id); % my $agent_list = $m->agent_list; % my $num = 1; % foreach my $data (@{$data_list}) { % my $dataname = $data->{'name'}; % my $dbname = $data->{'dbname'}; % my $size = $data->{'size'}; % my $source = $data->{'source'}; % my $stamp = $data->{'datetime'} || $data->{'stamp'} ; % my $newName = $dbname."-".$m->strip_sec($stamp); % $newName =~ s/[-\.: ]//g; % $newName =~ s/[+]//g; % my $modal_id = md5_sum($dataname); <div class="reveal" id="modal-data-restore-<%= $modal_id %>" data-reveal> <div class="text-center"> <h5>Restore database <%= $dbname %> as <%= $newName %></h5> </div> <form accept-charset="UTF-8" action="/agent/db/restore" method="get" data-abide novalidate> <input type="hidden" name="storeid" value="<%= $store_id %>" /> <input type="hidden" name="dataname" value="<%= $dataname %>" /> <input type="hidden" name="dbname" value="<%= $newName %>" /> <label>Agent hostname <select name="agentid" id="agent-hostname" required> <option value=""></option> % foreach my $agent (@{$agent_list}) { % my $agentName = $agent->{'hostname'}; % my $agent_id = $agent->{'id'}; <option value="<%= $agent_id %>"><%= $agentName %></option> % } </select> </label> <label>New name <input type="text" name="newname" value="<%= $newName %>" 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-data-restore-<%= $modal_id %>" type="button">Escape</button> </p> </form> <button class="close-button" data-close="modal-data-restore-<%= $modal_id %>" type="button">×</button> </div> <div class="reveal" id="modal-data-delete-<%= $modal_id %>" data-reveal> <div class="text-center"> <h5>Do you want to delete the data?</h5> <a class="button" data-close="modal-data-delete-<%= $modal_id %>">Escape</a> <a class="button alert" href="/store/data/delete?id=<%= $store_id %>&dataname=<%= url_escape $dataname %>">Delete</a> </div> <button class="close-button" data-close="modal-data-delete-<%= $modal_id %>" type="button">×</button> </div> <div class="reveal" id="modal-store-data-<%= $modal_id %>" data-reveal> <div class="text-center"> <h5>Store <%= $store_hostname %></h5> <div class="stacked-for-small button-group"> <a class="button" href="#" data-open="modal-data-restore-<%= $modal_id %>">Restore</a> <a class="button alert" href="#" data-open="modal-data-delete-<%= $modal_id %>">Delete</a> </div> </div> <button class="close-button" data-close="modal-store-data-<%= $modal_id %>" aria-label="Close modal" type="button"> <span aria-hidden="true">×</span> </button> </div> % } <div class="text-center"> <h5> Store <%= $store_hostname%> dataset list <a href="/store/data/list?id=<%= $store_id %>"> <i class="fi-refresh" style="font-size:1.3rem;"></i> </a> </h5> </div> <table id="table" class="display" > <thead> <tr> <td>#</td> <td>dbdump</td> <td class="show-for-medium">local date</td> <td class="show-for-large">orig date</td> <td>size</td> <td class="show-for-medium">source</td> </tr> </thead> <tbody> % $num = 1; % foreach my $data (@{$data_list}) { % my $filename = $data->{'name'}; % my $dataname = $data->{'name'}; % my $dbname = $data->{'dbname'}; % my $stamp = $data->{'stamp'}; % my $size = $data->{'size'}; % my $datetime = $data->{'datetime'}; % my $tz = $data->{'tz'}; % my $source = $data->{'source'}; % my $modal_id = md5_sum($dataname); <tr> <td><%= $num %></td> <td><a href="#" data-open="modal-store-data-<%= $modal_id %>"><%= $dbname %></a></td> <td class="show-for-medium"><%= $m->strip_sec($stamp) %></td> <td class="show-for-large"><%= $m->strip_sec($datetime)." ".$tz %></td> <td><%= $m->size_m($size) %></td> <td class="show-for-medium"><%= $source %></td> </tr> % $num++; % } </tbody> </table> <script> $.extend(true, $.fn.dataTable.defaults, { "searching": true, "ordering": true, "lengthChange": true, "language": { "search": "", "lengthMenu": "_MENU_", }, } ); $(document).ready(function() { $('#table').DataTable(); }); </script> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); % use Encode qw(decode encode); % my $m = $self->app->model; % my $store_id = $req->param('id'); % my $dataname = $req->param('dataname'); % my $job_id = $m->job_create; % $m->job_update( % $job_id, % type => 'storedelete', % sourceid => $store_id, % destid => $store_id, % author => $c->session('username') % ); % my $res = $m->store_delete($store_id); % if ($res) { <div class="callout success"> Store id <%= $store_id %> was deleted successful. </div> % $m->job_update($job_id, status => 'done', error => 'noerr', stop => $m->timestamp); % }; % unless ($res) { <div class="callout alert"> Store id <%= $store_id %> was deleted unsuccessful. </div> % $m->job_update($job_id, status => 'error', error => 'storedelerr', stop => $m->timestamp); % } <div class="text-center"> <a class="button" href="/store/list">Store List</a> </div> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(b64_encode b64_decode md5_sum dumper); % use Encode qw(decode encode); % my $m = $self->app->model; % my $store_list = $m->store_list; % foreach my $store (@{$store_list}) { % my $store_id = $store->{'id'}; % my $hostname = $store->{'hostname'}; % my $username = $store->{'username'}; % my $password = $store->{'password'}; % my $free = $m->store_free($store_id) || 0; <div class="reveal" id="modal-store-config-<%= $store_id %>" data-reveal> <form accept-charset="UTF-8" action="/store/config" method="get" data-abide novalidate> <h5>Configuration agent <%= $hostname %></h5> <input type="hidden" name="id" value="<%= $store_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 success">Accept</button> </p> </form> <button class="close-button" data-close="modal-store-config-<%= $store_id %>" type="button">×</button> </div> <div class="reveal" id="modal-store-<%= $store_id %>" data-reveal> <div class="text-center"> <h5>Store <%= $hostname %></h5> <div class="stacked-for-small button-group"> <a class="button" href="#" data-open="modal-store-config-<%= $store_id %>">ReConf</a> <a class="button" href="/store/data/list?id=<%= $store_id %>">DataList</a> <a class="button alert" href="#" data-open="modal-store-delete-<%= $store_id %>">Delete</a> </div> </div> <button class="close-button" data-close="modal-store-<%= $store_id %>" type="button">×</button> </div> <div class="reveal" id="modal-store-delete-<%= $store_id %>" data-reveal> <div class="text-center"> <h5>Do you want to delete store <%= $hostname %>?</h5> <a class="button" data-close="modal-store-delete-<%= $store_id %>">Escape</a> <a class="button alert" href="/store/delete?id=<%= $store_id %>">Delete</a> </div> <button class="close-button" data-close="modal-store-delete-<%= $store_id %>" type="button">×</button> </div> % } <h5 class="text-center">Store list <a href="/store/list"><i class="fi-refresh" style="font-size:1.3rem;"></i></a></h5> <table id="table" class="display" > <thead> <tr> <td>#</td> <td>hostname</td> <td>count</td> <td>total</td> <td>free</td> </tr> </thead> <tbody> % my $num = 1; % foreach my $store (@{$store_list}) { % my $store_id = $store->{'id'}; % my $hostname = $store->{'hostname'}; % my $store_info = $m->store_info($store_id); % my $sum = $store_info->{'sum'}; % my $count = $store_info->{'count'}; % my $free = $m->store_free($store_id) || 0; <tr> <td><%= $num %></td> <td><a href="#" data-open="modal-store-<%= $store_id %>"><%= $hostname %></a></td> <td><a href="/store/data/list?id=<%= $store_id %>"><%= $count %></a></td> <td><%= $m->size_m($sum) if $sum %></td> <td><%= $m->size_m($free) if $free %></td> </tr> % $num++; % } </tbody> </table> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'Dumper'; % use Mojo::Util qw(dumper); %#EOF