RecVi - mini-tool for PBX REcord VIsualisation and download, recvi for short.
It is minimal one-page application, written during ~1.5-2 hours.
#!/usr/bin/env perl #------------------ #--- CONTROLLER --- #------------------ package Recvi::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 hello { my $self = shift; $self->render(template => 'hello'); } #--- controller::agent --- 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 = 'audio/vnd.wave'; # 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 sessionAuth { return 1 if shift->session('username'); return undef; } sub checkPassword { my ($self, $username, $password) = @_; return undef unless $username; return undef unless $password; my $passwdFile = $self->app->config('pwdfile'); do { $self->app->log->error("Cannot read password file '$passwdFile'"); return undef; } unless -r $passwdFile; my $result = undef; eval { my $ht = Apache::Htpasswd->new({ passwdFile => $passwdFile, ReadOnly => 1 }); $result = $ht->htCheckPassword($username, $password); }; do { $self->app->log->debug("Auth module error: $@"); return undef; } if $@; return 1 if $result; $self->app->log->info("Bad auth from ".$self->tx->remote_address); return undef; } sub login { my $self = shift; return $self->redirect_to('/hello') if $self->sessionAuth; my $username = $self->req->param('username') || undef; my $password = $self->req->param('password') || undef; my $auth = $self->checkPassword($username, $password); if ($auth) { $self->session(username => $username); return $self->redirect_to('/hello'); } $self->render(template => 'login', req => $self->req); } sub dataList { my $self = shift; $self->render(template => 'dataList'); } sub dataDown { my $self = shift; my $name = $self->req->param('name'); return $self->render(template => 'not_found.production') unless $name; my $datadir = $self->app->config("datadir"); my $file = "/$datadir/$name"; return $self->render(template => 'not_found.production') unless -r $file; return $self->render(template => 'not_found.production') if -d $file; $self->renderFile(filepath => "$file"); } 1; #----------- #--- APP --- #----------- package Recvi; 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); 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 $appfile = abs_path(__FILE__); my $appname = basename($appfile, ".pl"); $0 = $appfile; getopt 'h|help' => \my $help, '4|ipv4listen=s' => \my $ipv4listen, '6|ipv6listen=s' => \my $ipv6listen, 'c|config=s' => \my $conffile, 'p|pwdfile=s' => \my $pwdfile, 'd|datadir=s' => \my $datadir, 'l|logfile=s' => \my $logfile, 'i|pidfile=s' => \my $pidfile, 'v|loglevel=s' => \my $loglevel, '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 -4 | --ipv4listen=address:port Listen address and port, defaults 127.0.0.1:5100 -6 | --ipv6listen=[address]:port Listen address and port, defaults [::1]:5100 -c | --config=path Path to config file -p | --pwdfile=path Path to user password file -d | --datadir=path Path to application files -l | --logfile=path Path to log file -i | --pidfile=path Path to process ID file -v | --loglevel=level Verbose level: debug, info, warn, error, fatal -u | --user=user System owner of process -g | --group=group System group -f | --nofork Dont fork process, for debugging All path option override option from configuration file )."\n"; exit 0; } my $server = Mojo::Server::Prefork->new; my $app = $server->build_app('Recvi'); $app = $app->controller_class('Recvi::Controller'); $app->config( hostname => hostname, datadir => $datadir || "/data", listenIPv4 => $ipv4listen || "0.0.0.0", listenIPv6 => $ipv6listen || "[::]", listenPort => "3005", pwdfile => $pwdfile || "@APP_CONFDIR@/$appname.pw", pidfile => $pidfile || "@APP_RUNDIR@/$appname.pid", logfile => $logfile || "@APP_LOGDIR@/$appname.log", conffile => $conffile || "@APP_CONFDIR@/$appname.conf", maxrequestsize => 1024*1024*1024, tlscert => "@APP_CONFDIR@/$appname.crt", tlskey => "@APP_CONFDIR@/$appname.key", appuser => $user || "@APP_USER@", appgroup => $group || "@APP_GROUP@", mode => 'production', loglevel => $loglevel || 'info', libdir => '@APP_LIBDIR@', ); $conffile = $app->config('conffile'); do { $app->log->debug("Load configuration from $conffile "); my $config = $app->plugin( 'JSONConfig', { file => $conffile } ); } if -r $conffile; $app->log->level($app->config('loglevel')); my $tlscert = $app->config('tlscert'); my $tlskey = $app->config('tlskey'); $datadir = $app->config('datadir'); my $rundir = dirname ($app->config('pidfile')); my $logdir = dirname ($app->config('logfile')); $pwdfile = $app->config('pwdfile'); do { print "Error: Cannot read from data direcory $datadir\n"; exit 1; } unless -r $datadir; do { print "Error: Cannot write to run direcory $rundir\n"; exit 1; } unless -w $rundir; do { print "Error: Cannot write to log direcory $logdir\n"; exit 1; } unless -w $logdir; do { print "Error: Cannot read TLS certificate $tlscert\n"; exit 1; } unless -r $tlscert; do { print "Error: Cannot read TLS key $tlskey\n"; exit 1; } unless -r $tlskey; do { print "Error: Cannot read password file $pwdfile\n"; exit 1; } unless -r $pwdfile; my $appUser = $app->config('appuser'); my $appGroup = $app->config('appgroup'); my $appUID = getpwnam($appUser); my $appGID = getgrnam($appGroup); do { print "System user $appUser not exist.\n"; exit 1; } unless $appUID; do { print "System group $appGroup not exist.\n"; exit 1; } unless $appGID; $app->moniker($appname); $app->mode($app->config("mode")); $app->secrets(['xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx']); $app->hook(before_dispatch => sub { my $c = shift; my $remoteIPaddr = $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; unless ($loglevel eq 'debug') { my $url = $c->req->url->to_abs->to_string; $c->app->log->info("$method $url from $remoteIPaddr"); } if ($loglevel eq 'debug') { my $url = $c->req->url->to_abs->to_string; $c->app->log->debug("$method $url from $remoteIPaddr"); } }); $app->static->paths->[0] = $app->config('libdir').'/public'; $app->renderer->paths->[0] = $app->config('libdir').'/templs'; my $r = $app->routes; $r->add_condition( auth => sub { my ($route, $c, $captures, $hash) = @_; return 1 if $c->sessionAuth; return undef; } ); $r->any('/login')->to('Controller#login'); $r->any('/hello')->over('auth')->to('Controller#hello'); $r->any('/data/list')->over('auth')->to('Controller#dataList'); $r->any('/data/down')->over('auth')->to('Controller#dataDown'); $app->helper('reply.not_found' => sub { my $c = shift; return $c->redirect_to('/login') unless $c->sessionAuth; $c->render(template => 'not_found.production'); }); my $tlsParam .= '?'; $tlsParam .= 'cert='.$tlscert; $tlsParam .= '&key='.$tlskey; my $listenPort = $app->config('listenPort'); my $listenIPv4 = $app->config('listenIPv4'); my $listenIPv6 = $app->config('listenIPv6'); $server->listen([ "https://$listenIPv4:$listenPort$tlsParam", ]); $server->pid_file($app->config('pidfile')); $server->heartbeat_interval(3); $server->heartbeat_timeout(60); unless ($nofork) { my $pid = fork; if ($pid == 0) { setuid($appUID) if $appUID; setgid($appGID) if $appGID; $app->log(Mojo::Log->new( path => $app->config('logfile') )); open (my $STDOUT2, '>&', STDOUT); open (STDOUT, '>>', '/dev/null'); open (my $STDERR2, '>&', STDERR); open (STDERR, '>>', '/dev/null'); chdir($datadir); 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; } } else { setuid($appUID) if $appUID; setgid($appGID) if $appGID; $server->run; } #EOF
%#
%# $Id$
%#
% layout 'default';
% title 'RecVi';
% use Mojo::Util qw(dumper);
% use File::Basename;
% use File::stat;
% use POSIX;
% use utf8;
% use strict;
% my $datadir = $self->app->config("datadir");
% unless (-d $datadir || -r $datadir) {
<div class="callout alert">
Cannot read data directory. Не могу прочитать каталог данных.
</div>
%}
% if (-d $datadir || -r $datadir) {
<h5 class="text-center">
Record list
<a href="/data/list"><i class="fi-refresh" style="font-size:1.3rem;"></i></a>
</h5>
% my $num = 1;
<table id="table" class="display" >
<thead>
<tr>
<td>#</td>
<td>date</td>
<td>source</td>
<td>dest</td>
<td>duration</td>
<td>size</td>
</tr>
</thead>
<tbody>
% opendir(my $dh, $datadir);
% 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 $size = $st->size;
% next if $size < 101;
% my $mtime = strftime("%Y-%m-%d %H:%M:%S %Z", localtime($st->mtime));
% my $rday = strftime("%j", localtime($st->mtime));
% my $cday = strftime("%j", localtime(time));
% my $diff = $cday-$rday;
% next if ($diff > 45);
% my ($Y, $M, $D, $h, $m, $s, $src, $dst) =
% $name =~ /rec-([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{2})-([0-9]{2})-([0-9]{2})-([0-9]{3})-([0-9]{1,64})/g;
% my $timestamp = "$Y-$M-$D $h:$m";
<tr>
<td><%= $num %></td>
<td><a target="_blank" href="/data/down?name=<%= $name %>"><%= $timestamp %></a></td>
<td><%= $src %></a></td>
<td><%= $dst %></a></td>
<td><%= int($size/16000+0.5) %></td>
<td><%= $size %></td>
</tr>
% $num++;
% }
% closedir $dh;
</tbody>
</table>
<script>
$.extend(true, $.fn.dataTable.defaults, {
"searching": true,
"ordering": true
} );
$(document).ready(function() {
$('#table').DataTable();
});
</script>
% }
%#EOF
AC_INIT(recvi.pl)
AM_INIT_AUTOMAKE(recvi,0.01)
AC_PREFIX_DEFAULT(/usr/local)
PACKAGE=recvi
AC_CHECK_PROG(HAVE_PERL, perl, true, false, /usr/local/bin /usr/bin)
if test "x$HAVE_PERL" = "xfalse"; then
AC_MSG_ERROR([Requested program perl not found])
fi
AC_SUBST(PERL, perl)
AC_PATH_PROG([PERL],[perl])
AC_PROG_INSTALL
AC_CANONICAL_HOST
case $host_os in
*freebsd* )
AC_SUBST(ROOT_GROUP, "wheel")
AM_CONDITIONAL(FREEBSD_OS, true)
AM_CONDITIONAL(LINUX_OS, false)
OSNAME=freebsd
ROOT_GROUP=wheel
;;
*linux* )
AC_SUBST(ROOT_GROUP, "root")
AM_CONDITIONAL(FREEBSD_OS, false)
AM_CONDITIONAL(LINUX_OS, true)
OSNAME=linux
ROOT_GROUP=root
;;
esac
AC_ARG_WITH(confdir,
AS_HELP_STRING([--with-confdir=PATH],[set configuration dir to PATH (default: "${ac_default_prefix}"/etc/${PACKAGE})]),
[ if test ! -z "$with_confdir" ; then
case $with_app_confdir in
/*)
APP_CONFDIR="$with_confdir"
;;
*)
AC_MSG_ERROR(You must specify an absolute path to --with-confdir=PATH)
;;
esac
else
APP_CONFDIR="$ac_default_prefix/etc/${PACKAGE}"
fi ],
[
APP_CONFDIR="$ac_default_prefix/etc/${PACKAGE}"
])
AC_DEFINE_UNQUOTED(APP_CONFDIR, "$APP_CONFDIR", [location of configuration files for ${PACKAGE}])
AC_SUBST(APP_CONFDIR, "$APP_CONFDIR")
AC_ARG_WITH(logdir,
AS_HELP_STRING([--with-logdir=PATH],[set file path for source logdir (default: /var/log/${PACKAGE}/${PACKAGE}.log)]),
[ if test ! -z "$with_logdir" ; then
case $with_logdir in
/*)
APP_LOGDIR="$with_logdir"
;;
*)
AC_MSG_ERROR(You must specify an absolute path to --with-logdir=PATH)
;;
esac
else
APP_LOGDIR="/var/log/${PACKAGE}"
fi ],
[
APP_LOGDIR="/var/log/${PACKAGE}"
])
AC_DEFINE_UNQUOTED(APP_LOGDIR, "$APP_LOGDIR", [location of ${PACKAGE} logdir])
AC_SUBST(APP_LOGDIR, "$APP_LOGDIR")
AC_ARG_WITH(rundir,
AS_HELP_STRING([--with-rundir=PATH],[set file path for source rundir (default: /var/run/${PACKAGE})]),
[ if test ! -z "$with_rundir" ; then
case $with_rundir in
/*)
APP_RUNDIR="$with_rundir"
;;
*)
AC_MSG_ERROR(You must specify an absolute path to --with-rundir=PATH)
;;
esac
else
APP_RUNDIR="/var/run/${PACKAGE}"
fi ],
[
APP_RUNDIR="/var/run/${PACKAGE}"
])
AC_DEFINE_UNQUOTED(APP_RUNDIR, "$APP_RUNDIR", [location of pid file])
AC_SUBST(APP_RUNDIR, "$APP_RUNDIR")
dnl AC_ARG_WITH(dbdir,
dnl AS_HELP_STRING([--with-dbdir=PATH],[set file path for data files (default: "/var/db/${PACKAGE}")]),
dnl [ if test ! -z "$with_dbdir" ; then
dnl case $with_dbdir in
dnl /*)
dnl APP_DBDIR="$with_dbdir"
dnl ;;
dnl *)
dnl AC_MSG_ERROR(You must specify an absolute path to --with-dbdir=PATH)
dnl ;;
dnl esac
dnl else
dnl APP_DBDIR="/var/db/${PACKAGE}"
dnl fi ],
dnl [ APP_DBDIR="/var/db/${PACKAGE}" ])
dnl AC_DEFINE_UNQUOTED(APP_DBDIR, "$APP_DBDIR", [location of application data])
dnl AC_SUBST(APP_DBDIR, "$APP_DBDIR")
default_storedir="/var/pgstore"
AC_ARG_WITH(storedir,
AS_HELP_STRING([--with-storedir=PATH],[set data directory for pgstore (default: $default_storedir)]),
[ if test ! -z "$with_storedir" ; then
case $with_storedir in
/*)
PGSTORE_DATADIR="$with_storedir"
;;
*)
AC_MSG_ERROR(You must specify an absolute path to --with-storedir=PATH)
;;
esac
else
PGSTORE_DATADIR="$default_storedir"
fi ],
[
PGSTORE_DATADIR="$default_storedir"
])
AC_DEFINE_UNQUOTED(PGSTORE_DATADIR, "$PGSTORE_DATADIR", [location of pgstore data dir])
AC_SUBST(PGSTORE_DATADIR, "$PGSTORE_DATADIR")
case $host_os in
*freebsd* )
default_user="www"
default_group="www"
;;
*linux* )
default_user="www-data"
default_group="www-data"
;;
esac
AC_ARG_WITH(user,
AS_HELP_STRING([--with-user=${PACKAGE}],[set executing user name]),
[ if test ! -z "$with_user" ; then
case $with_user in
"")
AC_MSG_ERROR(You must specify user name)
;;
*)
APP_USER="$with_user"
;;
esac
else
APP_USER="$default_user"
fi ],
[ APP_USER="$default_user" ])
AC_DEFINE_UNQUOTED(APP_USER, "$APP_USER", [effective user])
AC_SUBST(APP_USER, "$APP_USER")
AC_ARG_WITH(group,
AS_HELP_STRING([--with-group=${PACKAGE}],[set executing group name]),
[ if test ! -z "$with_group" ; then
case $with_group in
"")
AC_MSG_ERROR(You must specify group name)
;;
*)
APP_GROUP="$with_group"
;;
esac
else
APP_GROUP="$default_group"
fi ],
[ APP_GROUP="$default_group" ])
AC_DEFINE_UNQUOTED(APP_GROUP, "$APP_GROUP", [effective group id])
AC_SUBST(APP_GROUP, "$APP_GROUP")
AC_DEFINE_UNQUOTED(APP_LIBDIR, ${ac_default_prefix}/share/${PACKAGE}, [application lib directory])
AC_SUBST(APP_LIBDIR, ${ac_default_prefix}/share/${PACKAGE})
AC_DEFUN([AC_PERL_MODULES],[
ac_perl_modules="$1"
for ac_perl_module in $ac_perl_modules; do
AC_MSG_CHECKING(for perl module $ac_perl_module)
perl "-M$ac_perl_module" -e exit > /dev/null 2>&1
if test $? -ne 0; then
AC_MSG_RESULT(no);
AC_MSG_ERROR(You must install perl module $ac_perl_module)
else
AC_MSG_RESULT(ok);
fi
done])
AC_PERL_MODULES([
POSIX
Apache::Htpasswd
DBI
File::Basename
File::stat
Sys::Hostname
Mojo::Base
Mojo::Home
Mojo::IOLoop::Subprocess
Mojo::JSON
Mojo::Server::Prefork
Mojo::UserAgent
Mojo::Util
])
AC_OUTPUT([
Makefile
recvi:recvi.pl
rc.d/recvi
])
dnl EOF
# # $Id: Makefile.am 633 2017-04-15 13:51:07Z ziggi $ # AUTOMAKE_OPTIONS = foreign no-dependencies no-installinfo EXTRA_DIST = \ LICENSE install-data-hook: if FREEBSD_OS chmod a+x $(DESTDIR)/${etcdir}/rc.d/recvi endif $(INSTALL) -d -m 750 -o $(APP_USER) -g $(APP_GROUP) $(DESTDIR)$(APP_LOGDIR) $(INSTALL) -d -m 750 -o $(APP_USER) -g $(APP_GROUP) $(DESTDIR)$(APP_RUNDIR) for data in $(nobase_conf_DATA);do \ chmod 0644 $(DESTDIR)$(APP_CONFDIR)/$$data; \ done if FREEBSD_OS etcdir = @prefix@/etc nobase_etc_SCRIPTS = rc.d/recvi endif sbin_SCRIPTS = recvi confdir = @APP_CONFDIR@ nobase_conf_DATA = \ recvi.pw.example \ recvi.crt.example \ recvi.conf.example \ recvi.key.example nobase_dist_pkgdata_DATA = \ public/css/app.css \ public/css/datatables.css \ public/css/datatables.min.css \ public/css/foundation-float.css \ public/css/foundation-float.min.css \ public/css/foundation-prototype.css \ public/css/foundation-prototype.min.css \ public/css/foundation-rtl.css \ public/css/foundation-rtl.min.css \ public/css/foundation.css \ public/css/foundation.min.css \ public/favicon.ico \ public/favicon.png \ public/icons/foundation-icons.css \ public/icons/foundation-icons.eot \ public/icons/foundation-icons.svg \ public/icons/foundation-icons.ttf \ public/icons/foundation-icons.woff \ public/images/sort_asc_disabled.png \ public/images/sort_asc.png \ public/images/sort_both.png \ public/images/sort_desc_disabled.png \ public/images/sort_desc.png \ public/js/app.js \ public/js/datatables.js \ public/js/datatables.min.js \ public/js/foundation.js \ public/js/foundation.min.js \ public/js/jquery.js \ public/js/jquery.min.js \ public/js/what-input.js \ \ templs/dataList.html.ep \ templs/exception.development.html.ep \ templs/exception.production.html.ep \ templs/hello.html.ep \ templs/layouts/default.html.ep \ templs/not_found.development.html.ep \ templs/not_found.production.html.ep \ templs/login.html.ep #EOF
<!doctype html> <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><%= title %></title> <link rel="stylesheet" href="/css/foundation-float.min.css"> <link rel="stylesheet" href="/css/datatables.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/datatables.min.js"></script> <script src="/js/foundation.min.js"></script> </head> <body> <div class="title-bar" data-responsive-toggle="topbar-menu" data-hide-for="medium"> <button class="menu-icon" type="button" data-toggle="topbar-menu"></button> <div class="title-bar-title">Menu</div> </div> <div class="top-bar" id="topbar-menu"> <div class="top-bar-left"> <ul class="dropdown menu" data-dropdown-menu> <li class="menu-text">RecVi</li> <li><a href="/data/list">Data</a></li> </ul> </div> </div> <div class="row"> </div> <div class="row"> <!- end of head template -> <%= content %> <!- begin of tail template -> </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> <!- end of tail template -> <!- 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="text-center" style="margin:5em 5em 5em 5em;"> <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="somename" /> </label> <label>Password <input type="password" name="password" placeholder="password" /> </label> <p class="text-center"> <button type="submit" class="button">LogIn</button> </p> </form> </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