It is small web application for openvpn services start/stop and some network monitoring.
We must only set directory with openvpn configuration, default set as '/etc/openvpn'.
{ "confdir":"/etc/openvpn", "loglevel":"debug" }
I wrote first release of the application with around 5-6 hours total to replace a legacy WTF application with similar functions.
Now I made the app without TLS because it was necessary for install the app on old Ubuntu releases. Later I will shore add TLS.
Release 0.06 for Debian 8 start with SSL/TLS. Default listen only IPv4
deb http://pure.unix7.org/ubuntu/precise precise main
# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 72D340A3
#!@PERL@ #------------ #--- CRON --- #------------ package Cron; use strict; use warnings; sub new { my ($class, %args) = @_; my $self = {}; bless $self, $class; return $self; } sub ping { my $self = shift; my $res = "Pong!"; $res; } 1; #-------------- #--- DAEMON --- #-------------- package 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 = {}; bless $self, $class; return $self; } sub fork { my $self = 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'); getpid; } 1; package VPN; use strict; use warnings; use Mojo::Util qw(dumper); use File::Basename qw(basename dirname); use POSIX; sub new { my ($class, $app) = @_; my $self = { app => $app, }; bless $self, $class; return $self; } sub app { return shift->{app}; } sub conf_list { my ($self, $confdir) = @_; return undef unless $confdir; opendir(my $dh, $confdir); my @list; while (my $name = readdir($dh)) { next unless ($name =~ m/^\w{1,64}.conf$/); next if -d $name; push @list, "$confdir/$name"; } closedir $dh; return \@list; } sub conf_parse { my ($self, $filename) = @_; return undef unless $filename; return undef unless (-f $filename && -r $filename); open(my $fh, '<:encoding(UTF-8)', $filename) or return undef; my %list; while (my $row = <$fh>) { my $key; my $value; ($key, $value) = $row =~ /^(log)\s{1,20}([\/a-z0-9.]{1,64})/; $list{$key} = $value if $key; ($key, $value) = $row =~ /^(status)\s{1,20}([\/_A-Za-z0-9.]{1,64})/; $list{$key} = $value if $key; my $val2; ($key, $value, $val2) = $row =~ /^(server)\s{1,20}([\/0-9.]{1,64})\s{1,20}([\/0-9.]{1,64})/; $list{$key} = "$value/$val2" if $key; } return \%list; } sub stat_parse { my ($self, $filename) = @_; return undef unless $filename; return undef unless (-f $filename && -r $filename); open(my $fh, '<:encoding(UTF-8)', $filename) or return undef; my %hash; while (my $row = <$fh>) { my $key; my $value; #Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since my ($cn, $ipaddr, $recv, $sent, $total, $date) = $row =~ /([\w\-]{1,64}),([\/a-z0-9.]{1,64}):([0-9]{1,16}),([0-9]{1,16}),([0-9]{1,16}),([\w\: ]{1,20})/; $hash{$cn}{'ipaddr'} = $ipaddr if $cn; $hash{$cn}{'recv'} = $recv if $cn; $hash{$cn}{'sent'} = $sent if $cn; $hash{$cn}{'total'} = $total if $cn; $hash{$cn}{'date'} = $date if $cn; #Virtual Address,Common Name,Real Address,Last Ref my ($local, $cn2, $ipaddr2, $some, $last) = $row =~ /^([0-9.]{1,64}),([\w\-]{1,64}),([0-9.]{1,64}):([0-9]{1,16}),([\w\: ]{1,20})/; $hash{$cn2}{'local'} = $local if $local; $hash{$cn2}{'last'} = $last if $local; my ($net, $cn3, $ipaddr3, $some2, $last2) = $row =~ /^([0-9.]{1,64}\/[0-9]{2}),([\w\-]{1,64}),([0-9.]{1,64}):([0-9]{1,16}),([\w\: ]{1,20})/; push @{$hash{$cn3}{'net'}}, $net if $net; my ($wclient, $cn4, $ipaddr4, $some3, $last4) = $row =~ /^([0-9.]{1,64}C),([\w\-]{1,64}),([0-9.]{1,64}):([0-9]{1,16}),([\w\: ]{1,20})/; $wclient =~ s/C// if $wclient; push @{$hash{$cn4}{'wclient'}}, $wclient if $wclient; } return \%hash; } sub conf_basename { my ($self, $filename) = @_; return undef unless $filename; $filename = basename ($filename, ".conf"); return $filename if $filename; return undef; } sub system_comm { my ($self, $comm) = @_; return undef unless $comm; open HR, "$comm |" or return undef; my $out; while (my $str = <HR>) { $out .= $str; }; return $out; } sub service_status { my ($self, $name) = @_; return undef unless $name; # my $out = qx(service openvpn status $name 2>&1); my $out = $self->system_comm("sudo service openvpn status $name 2>&1") || ''; # $self->app->log->info("service_status: Status service $name"); return 'up' if $out =~ m/is running/; return 'down' if $out =~ m/is not running/; return undef; } sub service_start { my ($self, $name) = @_; return undef unless $name; # my $out = qx(service openvpn start $name 2>&1); my $out = $self->system_comm("sudo service openvpn start $name 2>&1") || ''; $self->app->log->info("service_start: Start service $name with result $out"); my $s = $self->service_status($name) || ''; return 1 if $s eq 'up'; undef; } sub service_stop { my ($self, $name) = @_; return undef unless $name; # my $out = qx(service openvpn stop $name 2>&1); my $out = $self->system_comm("sudo service openvpn stop $name 2>&1") || ''; $self->app->log->info("service_stop: Stop service $name with result $out"); my $s = $self->service_status($name) || ''; return 1 if $s eq 'down'; return undef; } sub pack_ipaddr { my ($self, $addr) = @_; return undef unless $addr; my ($ipaddr, $mask) = split "[/]", $addr; return undef unless $ipaddr; return undef unless $mask; my %mask2cidr = ( '255.255.255.255' => '32', '255.255.255.254' => '31', '255.255.255.252' => '30', '255.255.255.248' => '29', '255.255.255.240' => '28', '255.255.255.224' => '27', '255.255.255.192' => '26', '255.255.255.128' => '25', '255.255.255.0' => '24', '255.255.254.0' => '23', '255.255.252.0' => '22', '255.255.248.0' => '21', '255.255.240.0' => '20', '255.255.224.0' => '19', '255.255.192.0' => '18', '255.255.128.0' => '17', '255.255.0.0' => '16', '255.254.0.0' => '15', '255.252.0.0' => '14', '255.248.0.0' => '13', '255.240.0.0' => '12', '255.224.0.0' => '11', '255.192.0.0' => '10', '255.128.0.0' => '9', '255.0.0.0' => '8', '254.0.0.0' => '7', '252.0.0.0' => '6', '248.0.0.0' => '5', '240.0.0.0' => '4', '224.0.0.0' => '3', '192.0.0.0' => '2', '128.0.0.0' => '1', ); my $bitc = $mask2cidr{$mask} || undef; return undef unless $bitc; return "$ipaddr/$bitc"; } 1; package VPNsw::Controller; use utf8; use strict; use warnings; use Mojo::Base 'Mojolicious::Controller'; use Mojo::Util qw(md5_sum dumper quote encode); use Mojo::JSON qw(encode_json decode_json); use Apache::Htpasswd; use File::Basename qw(fileparse); sub vpn_list { my $self = shift; $self->render(template => 'vpn-list'); } sub vpn_info { my $self = shift; $self->render(template => 'vpn-info'); } sub hello { my $self = shift; $self->render(template => 'hello'); } sub index { my $self = shift; $self->redirect_to("/vpn/list"); } sub vpn_all { my $self = shift; $self->render(template => 'vpn-all'); } #---------------- #--- AJAX API --- #---------------- sub vpn_start { my $self = shift; my $service = $self->req->param('service'); return $self->render(json => {} ) unless $service; $self->app->vpn->service_start($service); my $status = $self->app->vpn->service_status($service); $self->render(json => {service => $service, status => $status } ); } sub vpn_stop { my $self = shift; my $service = $self->req->param('service'); return $self->render(json => {} ) unless $service; $self->app->vpn->service_stop($service); my $status = $self->app->vpn->service_status($service); $self->render(json => {service => $service, status => $status } ); } #-------------------- #--- 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('/'); } 1; #----------- #--- APP --- #----------- package VPNsw; 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 = 'vpnsw'; #-------------- #--- 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; } my $server = Mojo::Server::Prefork->new; my $app = $server->build_app('VPNsw'); $app = $app->controller_class('VPNsw::Controller'); $app->secrets(['6d578e43ba88260e0375a1a35fd7954b']); $app->static->paths(['@APP_LIBDIR@/public']); $app->renderer->paths(['@APP_LIBDIR@/templs']); $app->config(conffile => $conffile || '@APP_CONFDIR@/vpnsw.conf'); $app->config(pwdfile => '@APP_CONFDIR@/vpnsw.pw'); $app->config(logfile => '@APP_LOGDIR@/vpnsw.log'); $app->config(loglevel => 'info'); $app->config(pidfile => '@APP_RUNDIR@/vpnsw.pid'); $app->config(crtfile => '@APP_CONFDIR@/vpnsw.crt'); $app->config(keyfile => '@APP_CONFDIR@/vpnsw.key'); $app->config(listenaddr4 => '0.0.0.0'); #$app->config(listenaddr6 => '[::]'); $app->config(listenport => '1007'); $app->config(user => $user || '@APP_USER@'); $app->config(group => $group || '@APP_GROUP@'); $app->config(confdir => "/etc/openvpn"); if (-r $app->config('conffile')) { $app->log->debug("Load configuration from ".$app->config('conffile')); $app->plugin('JSONConfig', { file => $app->config('conffile') }); } #--------------- #--- HELPERS --- #--------------- $app->helper( vpn => sub { state $vpn = VPN->new($app); }); $app->helper( cron => sub { my $cron = Cron->new; $cron; }); $app->helper('reply.not_found' => sub { my $c = shift; return $c->redirect_to('/login') unless $c->session('username'); $c->render(template => 'not_found.production'); }); #-------------- #--- 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('/')->over('auth')->to('controller#index' ); $r->any('/hello')->over('auth')->to('controller#hello'); $r->any('/vpn/list')->over('auth')->to('controller#vpn_list'); $r->any('/vpn/info')->over('auth')->to('controller#vpn_info'); $r->any('/vpn/all')->over('auth')->to('controller#vpn_all'); $r->any('/j/vpn/start')->over('auth')->to('controller#vpn_start'); $r->any('/j/vpn/stop')->over('auth')->to('controller#vpn_stop'); $r->any('/j/vpn/status')->over('auth')->to('controller#vpn_status'); $r->any('/j/vpn/list')->over('auth')->to('controller#vpn_status'); #---------------- #--- LISTENER --- #---------------- my $tls = '?'; $tls .= 'cert='.$app->config('crtfile'); $tls .= '&key='.$app->config('keyfile'); my $listen4; if ($app->config('listenaddr4')) { $listen4 = "http://"; $listen4 .= $app->config('listenaddr4').':'.$app->config('listenport'); # $listen4 .= $tls; } my $listen6; if ($app->config('listenaddr6')) { $listen6 = "http://"; $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); #----------------- #--- DOEMINIZE --- #----------------- unless ($nofork) { my $d = 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') )); }; my $sub = Mojo::IOLoop::Subprocess->new; $sub->run( sub { my $subproc = shift; my $loop = Mojo::IOLoop->singleton; my $id = $loop->recurring( 1200 => sub { my $res = $app->cron->ping; $app->log->info($res); } ); $loop->start unless $loop->is_running; 1; }, sub { my ($subprocess, $err, @results) = @_; $app->log->info('Exit subprocess'); 1; } ); my $pid = $sub->pid; $app->log->info("Subrocess $pid start "); $server->on( finish => sub { my ($prefork, $graceful) = @_; $app->log->info("Subrocess $pid stop"); kill('INT', $pid); } ); $server->run; #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 'Bird'; % use Mojo::Util qw(dumper html_unescape unquote); <h5 class="text-center">Hi! How are you there?</h5> %#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 '404 Not found'; <h5>404 Page not found</h5> %#EOF
%# %# $Id: not_found.html.ep 627 2017-04-15 13:02:08Z ziggi $ %# % layout 'default'; % title '404 Not found'; <h5>404 Page not found</h5> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'VPN Sw'; % use Mojo::Util qw(dumper); <div class="button" id="start">Start All</div> <div class="button" id="stop">Stop All</div> <p> Please, don't close the page until all proceses are completed<br/> Proszę nie zamykać strony, aż wszystkie procesy są zakończone<br/> Будь ласка, не закривайте сторінку до завершення всіх процесів<br/> すべてのプロセスが完了するまでページを閉じないでください<br/> </p> <div id="log" class="callout"></div> % my $confdir = $self->app->config('confdir'); % my $vpn = $self->app->vpn; % my $conf_list = $vpn->conf_list($confdir); % my $str; % my $count = 0; % foreach my $conffile (sort @{$conf_list}) { % my $name = $vpn->conf_basename($conffile); % $str = $str.", '$name'"; % $count += 1; % } % $str =~ s/^,//; <script> function start_all() { var int = 1100; var count = <%== $count %>; $("#log").empty(); function doSetTimeout(n, service) { setTimeout(function() { let num = n + 1; $("#log").append(num + '/' + count + ": Start service " + service + " ... "); $.ajax({ dataType: 'json', url: '/j/vpn/start?service=' + service, success: function(data) { $("#log").append(data.status + '<br/>'); $('html,body').animate({ scrollTop: document.body.scrollHeight}, "slow" ); } }); }, n * int + int/2 + 100); } var list = [ <%== $str %>]; for (var i = 0; i < list.length; i++) { doSetTimeout(i, list[i]); } } function stop_all() { var int = 1100; var count = <%== $count %>; $("#log").empty(); function doSetTimeout(n, service) { setTimeout(function() { let num = n + 1; $("#log").append(num + '/' + count + ": Stop service " + service + " ... "); $.ajax({ dataType: 'json', url: '/j/vpn/stop?service=' + service, success: function(data) { $("#log").append(data.status + '<br/>'); $('html,body').animate({ scrollTop: document.body.scrollHeight}, "slow" ); } }); }, n * int + int/2 + 100); } var list = [ <%== $str %>]; for (var i = 0; i < list.length; i++) { doSetTimeout(i, list[i]); } } $("#start").dblclick(function() { start_all(); }); $("#stop").dblclick(function() { stop_all(); }); </script> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'VPN Sw'; % use Mojo::Util qw(dumper); % use File::Basename; % use File::stat; % use POSIX; % my $req = $c->req; % my $confdir = $self->app->config('confdir'); % my $vpn = $self->app->vpn; <div class="text-center"> <h5>Network details <a href="/vpn/info"><i class="fi-refresh""></i></a></h5> </div> % my $conf_list = $vpn->conf_list($confdir); <table id="table" class="table-scroll hover"> <thead> <tr> <th>#</th> <th>cn</th> <th>public</th> <th>service</th> <th>stat</th> <th>vpn net</th> <th>tun addr</th> <th>off addr</th> <th>wrk addr</th> </tr> </thead> <tbody> % my $num = 1; % foreach my $conffile (sort @{$conf_list}) { % my $name = $vpn->conf_basename($conffile); % my $conf = $vpn->conf_parse($conffile); % my $server_net = $conf->{'server'} || ''; % my $statfile = $conf->{'status'} || ''; % my $stat = $vpn->stat_parse($statfile) || undef; % my $conn = scalar keys %{$stat}; % my $netcount = 0; % foreach my $cn (keys %{$stat}) { % $netcount++ if $stat->{$cn}{'net'} % } % my $status = $vpn->service_status($name) || ''; % foreach my $cn (sort keys %{$stat}) { % my $ipaddr = $stat->{$cn}{'ipaddr'} || ''; % my $date = $stat->{$cn}{'date'} || ''; % my $peer = $stat->{$cn}{'local'} || ''; % my $nets = $stat->{$cn}{'net'} || undef ; % my $netstr; % foreach my $net (@{$nets}) { % $netstr .= "$net "; % } % $netstr ||= 'ws/nat'; % my $wclients = $stat->{$cn}{'wclient'} || () ; % my $wcstr = ''; % foreach my $wc (@{$wclients}) { % $wcstr .= "$wc "; % } <tr> <td><%= $num %></td> <td><%= $cn %></td> <td><%= $ipaddr %></td> <td><%= $name %></td> <td><%= $status %></td> <td><%= $vpn->pack_ipaddr($server_net) if $server_net %></td> <td><%= $peer %></td> <td><%= $netstr %></td> <td><%= $wcstr %></td> </tr> % $num++; % } % }; </table> <script> $.extend(true, $.fn.dataTable.defaults, { "searching": true, "ordering": true, "pageLength": -1, "lengthMenu": [ [10, 25, 50,100, -1], [10, 25, 50,100, "All"] ], "language": { "search": "", "lengthMenu": "_MENU_", "info": "_START_-_END_ of _TOTAL_", "infoEmpty": "", }, }); $(document).ready(function() { $('#table').DataTable(); }); </script> %#EOF
%# %# $Id$ %# % layout 'default'; % title 'VPN Sw'; % use Mojo::Util qw(dumper); % use File::Basename; % use File::stat; % use POSIX; % my $req = $c->req; % my $confdir = $self->app->config('confdir'); % my $vpn = $self->app->vpn; % my $request = $req->param('request') || ''; % my $service = $req->param('service') || undef; % $vpn->service_start($service) if ($request eq 'start' && defined $service); % $vpn->service_stop($service) if ($request eq 'stop' && defined $service); <div class="text-center"> <h5>VPN services <a href="/vpn/list"><i class="fi-refresh"></i></a></h5> </div> % my $conf_list = $vpn->conf_list($confdir); % foreach my $conffile (sort @{$conf_list}) { % my $name = $vpn->conf_basename($conffile); % my $conf = $vpn->conf_parse($conffile); % my $statfile = $conf->{'status'} || ''; % my $stat = $vpn->stat_parse($statfile) || undef; % my $status = $vpn->service_status($name) || ''; % my $action = 'stop'; % $action = 'start' if $status eq 'down'; %# <div class="reveal" id="modal-action-<%= $name %>" data-reveal> %# <div class="text-center"> %# <h5>Do switch status?</h5> %# </div> %# <form accept-charset="UTF-8" action="/vpn/list" method="get"> %# <input type="hidden" name="request" value="<%= $action %>" /> %# <input type="hidden" name="service" value="<%= $name %>" /> %# %# <p class="text-center"> %# <button type="submit" class="button alert">Yes, I agree</button> %# <button class="button" data-close="modal-action-<%= $name %>" type="button">No, Escape</button> %# </p> %# </form> %# <button class="close-button" data-close="modal-action-<%= $name %>" type="button">×</button> %# </div> <div class="reveal large" id="modal-<%= $name %>" data-reveal> <div> <h5 class="text-center">Service <%= $name %> </h5> % if ($stat) { % my $subnum = 1; <table class="table-scroll"> <thead> <tr> <th>cn</th> <th>ipaddr</th> <th>start</th> <th>peer</th> <th>remote</th> <th>wc</th> </tr> </thead> % foreach my $cn (sort keys %{$stat}) { % my $ipaddr = $stat->{$cn}{'ipaddr'} || ''; % my $date = $stat->{$cn}{'date'} || ''; % my $peer = $stat->{$cn}{'local'} || ''; % my $net = $stat->{$cn}{'net'} || 'ws '.$peer ; % my $nets = $stat->{$cn}{'net'} || undef ; % my $netstr; % foreach my $net (@{$nets}) { % $netstr .= "$net "; % } % $netstr ||= 'ws/nat'; % my $wclients = $stat->{$cn}{'wclient'} ; % my $wcstr = ''; % foreach my $wc (@{$wclients}) { % $wcstr .= "$wc "; % } <tr> <td><%= $cn %></td> <td><%= $ipaddr %></td> <td><%= $date %></td> <td><%= $peer %></td> <td><%= $netstr %></td> <td><%= $wcstr %></td> </tr> % $subnum++; % } </table> % } </div> <button class="close-button" data-close="modal-<%= $name %>" type="button">×</button> </div> % } % my $total_tun = 0; % my $total_tun_up = 0; % my $total_net = 0; % my $total_ws = 0; % my $totalConn = 0; % foreach my $conffile (sort @{$conf_list}) { % my $name = $vpn->conf_basename($conffile); % my $conf = $vpn->conf_parse($conffile); % my $server = $conf->{'server'} || ''; % my $statfile = $conf->{'status'} || ''; % my $stat = $vpn->stat_parse($statfile) || undef; % $total_tun = $total_tun + scalar (keys %{$stat}); % foreach my $cn (keys %{$stat}) { % $total_net++ if $stat->{$cn}{'net'}; % $total_ws++ unless $stat->{$cn}{'net'}; % } % foreach my $cn (keys %{$stat}) { % $total_ws = $total_ws + scalar @{$stat->{$cn}{'wclient'}} if $stat->{$cn}{'wclient'}; % } % my $status = $vpn->service_status($name) || ''; % my $request = 'start'; % $request = 'stop' if $status eq 'up'; % } <p class="text-center"> total tun:<%= $total_tun %> total ws:<%= $total_ws %> </p> <table id="table" class="table-scroll" > <thead> <tr> <th>#</th> <th>service</th> <th>status</th> <th>#tun</th> <th>#net</th> <th>#wc</th> </tr> </thead> <tbody> % my $num = 1; % foreach my $conffile (sort @{$conf_list}) { % my $name = $vpn->conf_basename($conffile); % my $conf = $vpn->conf_parse($conffile); % my $server = $conf->{'server'} || ''; % my $statfile = $conf->{'status'} || ''; % my $stat = $vpn->stat_parse($statfile) || undef; % my $conn = scalar keys %{$stat}; % my $net_count = 0; % my $wc_count = 0; % foreach my $cn (keys %{$stat}) { % $net_count++ if $stat->{$cn}{'net'}; % $wc_count++ unless $stat->{$cn}{'net'}; % } % foreach my $cn (keys %{$stat}) { % $wc_count = $wc_count + @{$stat->{$cn}{'wclient'}} if $stat->{$cn}{'wclient'}; % } % my $status = $vpn->service_status($name) || ''; % my $request = 'start'; % $request = 'stop' if $status eq 'up'; % my $stat_icon = 'fi-x'; % $stat_icon = 'fi-play' if $status eq 'up'; % my $stat_color = 'alert'; % $stat_color = 'success' if $status eq 'up'; <tr> <td><%= $num %></td> <td><%= $name %></td> <td><a id="status_<%= $name %>"><%= $status %> <i class="<%= $stat_icon %>"></i></a></td> <td><a href="#" data-open="modal-<%= $name %>"><%= $conn if $conn %></a></td> <td><%= $net_count if $net_count %></td> <td><%= $wc_count if $wc_count %></td> </tr> % $num++; % } </table> % foreach my $conffile (sort @{$conf_list}) { % my $name = $vpn->conf_basename($conffile); % my $status = $vpn->service_status($name) || ''; <script> var status_<%= $name %> = "<%= $status %>"; $("#status_<%= $name %>").dblclick(function() { if (status_<%= $name %> == 'down') { $.ajax({ dataType: 'json', url: '/j/vpn/start?service=<%= $name %>', success: function(data) { if (data.status == 'up') { $("#status_<%= $name %>").html('up <i class="fi-play"></i>'); status_<%= $name %> = 'up'; } } }); } else { $.ajax({ dataType: 'json', url: '/j/vpn/stop?service=<%= $name %>', success: function(data) { if (data.status == 'down') { $("#status_<%= $name %>").html('down <i class="fi-x"></i>'); status_<%= $name %> = 'down'; } } }); } }); </script> % } <script> $.extend(true, $.fn.dataTable.defaults, { "searching": true, "ordering": true, "pageLength": -1, "lengthMenu": [ [10, 25, 50,100, -1], [10, 25, 50,100, "All"] ], "language": { "search": "", "lengthMenu": "_MENU_", "info": "_START_-_END_ of _TOTAL_", "infoEmpty": "", }, }); $(document).ready(function() { $('#table').DataTable(); }); </script> %#EOF
— Borodin Oleg 2017/12/13 13:18