feat: add web login, logout and runtime logging improvements
This commit is contained in:
3
apply.pl
3
apply.pl
@@ -271,6 +271,7 @@ EOF
|
||||
}
|
||||
|
||||
redirect( '/settings/users' );
|
||||
return( 1 );
|
||||
}
|
||||
elsif( $c[1] eq 'groups' )
|
||||
{
|
||||
@@ -324,6 +325,7 @@ EOF
|
||||
}
|
||||
|
||||
redirect( '/settings/groups' );
|
||||
return( 1 );
|
||||
}
|
||||
elsif( $c[1] eq 'queues' )
|
||||
{
|
||||
@@ -393,6 +395,7 @@ EOF
|
||||
}
|
||||
|
||||
redirect( '/settings/queues' );
|
||||
return( 1 );
|
||||
}
|
||||
|
||||
elsif( $c[1] eq 'advanced' )
|
||||
|
||||
@@ -8,6 +8,8 @@ Documentation=man:systemd.service(5)
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
RuntimeDirectory=mars-nwe-webui
|
||||
RuntimeDirectoryMode=0700
|
||||
WorkingDirectory=/
|
||||
|
||||
ExecStartPre=/bin/mkdir -p @MARS_NWE_LOG_DIR@
|
||||
|
||||
47
settings.pl
47
settings.pl
@@ -40,7 +40,8 @@ sub settings_nav_bar()
|
||||
return <<'EOF_NAV';
|
||||
<div class="settings-nav" style="position:sticky;top:0;z-index:20;margin:0 0 14px 0;padding:10px;background:#fffdf9;border:1px solid #ddcfba;border-radius:12px;box-shadow:0 4px 12px rgba(80,55,30,0.06);">
|
||||
<a class="settings-nav-back" style="display:inline-block;margin-right:10px;padding:7px 12px;border-radius:9px;background:#6d5d53;color:#fff;text-decoration:none;font-weight:bold;" href="javascript:history.back()">Back</a>
|
||||
<a class="settings-nav-main" style="display:inline-block;padding:7px 12px;border-radius:9px;background:#a32020;color:#fff;text-decoration:none;font-weight:bold;" href="/" target="_top">Main menu</a>
|
||||
<a class="settings-nav-main" style="display:inline-block;margin-right:10px;padding:7px 12px;border-radius:9px;background:#a32020;color:#fff;text-decoration:none;font-weight:bold;" href="/" target="_top">Main menu</a>
|
||||
<a class="settings-nav-logout" style="display:inline-block;padding:7px 12px;border-radius:9px;background:#8b4a1d;color:#fff;text-decoration:none;font-weight:bold;" href="/logout" target="_top">Logout</a>
|
||||
</div>
|
||||
EOF_NAV
|
||||
}
|
||||
@@ -708,40 +709,44 @@ sub unix_user_defaults_from_query()
|
||||
|
||||
my $want = '';
|
||||
|
||||
# Preferred import route:
|
||||
# /settings/users_import/<unixuser>
|
||||
if( defined( $c[1] ) && $c[1] eq 'users_import' && defined( $c[2] ) && $c[2] ne '' )
|
||||
# The router puts imported users here after /settings/users_import/<user>
|
||||
# was normalized to /settings/users/add_new.
|
||||
if( defined( $p{unix_user} ) && $p{unix_user} ne '' )
|
||||
{
|
||||
$want = $p{unix_user};
|
||||
}
|
||||
# Compatibility for direct /settings/users_import/<user>, before normalization.
|
||||
elsif( defined( $c[1] ) && $c[1] eq 'users_import' && defined( $c[2] ) && $c[2] ne '' )
|
||||
{
|
||||
$want = $c[2];
|
||||
}
|
||||
# Compatibility route:
|
||||
# /settings/users/add_new/<unixuser>
|
||||
# Compatibility for /settings/users/add_new/<user>.
|
||||
elsif( defined( $c[3] ) && $c[3] ne '' )
|
||||
{
|
||||
$want = $c[3];
|
||||
}
|
||||
elsif( defined( $p{unix_user} ) && $p{unix_user} ne '' )
|
||||
{
|
||||
$want = $p{unix_user};
|
||||
}
|
||||
|
||||
if( $want ne '' )
|
||||
{
|
||||
$want =~ s/[^-_\.A-Za-z0-9]//g;
|
||||
|
||||
foreach my $u ( unix_userlist() )
|
||||
{
|
||||
next if ! defined( $u->{name} ) || $u->{name} ne $want;
|
||||
# Minimal import: use the Unix user parameter that reaches settings.pl.
|
||||
$defaults{unix_user} = $want;
|
||||
|
||||
my $gecos = defined( $u->{gecos} ) ? $u->{gecos} : '';
|
||||
$gecos =~ s/,.*$//;
|
||||
$defaults{name} = uc( $want );
|
||||
$defaults{name} =~ s/[^-_\.A-Za-z0-9]//g;
|
||||
|
||||
$defaults{name} = uc( $u->{name} );
|
||||
$defaults{name} =~ s/[^-_\.A-Za-z0-9]//g;
|
||||
$defaults{unix_user} = $u->{name};
|
||||
$defaults{fullname} = $gecos;
|
||||
last;
|
||||
}
|
||||
# Step 2: if no GECOS/fullname is available yet, use a friendly
|
||||
# fallback from the Unix account name. "mario" -> "Mario",
|
||||
# "mario_fetka" -> "Mario Fetka".
|
||||
my $full = $want;
|
||||
$full =~ s/[_\.\-]+/ /g;
|
||||
$full =~ s/\s+/ /g;
|
||||
$full =~ s/^\s+//;
|
||||
$full =~ s/\s+$//;
|
||||
$full = join( ' ', map { ucfirst( lc( $_ ) ) } split( / /, $full ) );
|
||||
|
||||
$defaults{fullname} = $full;
|
||||
}
|
||||
|
||||
return %defaults;
|
||||
|
||||
450
smart.cmake
450
smart.cmake
@@ -31,7 +31,21 @@ do( '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf' )
|
||||
or die "Could not load @MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf: $@ $!";
|
||||
|
||||
close( STDERR );
|
||||
open( STDERR, '>>' . $smart_log_path )
|
||||
|
||||
# Prefix all raw STDERR from helper tools with timestamp/component before it
|
||||
# reaches smart.log. This also catches output from nwbols/nwbpset/nwpasswd
|
||||
# and systemctl warnings.
|
||||
my $smart_stderr_filter = "perl -MPOSIX=strftime -ne 'chomp; " .
|
||||
"my \\$v=\\$ENV{SMART_VERSION}||q{0.99.pl28}; " .
|
||||
"my \\$f=\\$ENV{SMART_LOG_FILE}||q{stderr}; " .
|
||||
"print strftime(q{[%Y-%m-%d %H:%M:%S]}, localtime), qq{ [ERROR] [SMArT \\$v] [\\$f] \\$_\\n};' >> " .
|
||||
quotemeta( $smart_log_path );
|
||||
|
||||
$ENV{SMART_VERSION} = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28';
|
||||
$ENV{SMART_LOG_FILE} = 'stderr';
|
||||
|
||||
open( STDERR, '|-', $smart_stderr_filter )
|
||||
or open( STDERR, '>>' . $smart_log_path )
|
||||
or die "Could not open $smart_log_path: $!";
|
||||
|
||||
$ENV{HOME} = '@MARS_NWE_INSTALL_FULL_CONFDIR@';
|
||||
@@ -45,11 +59,14 @@ $smart_systemctl_path = '@SYSTEMCTL_EXECUTABLE@' unless defined $smart_systemctl
|
||||
$l = <STDIN>;
|
||||
$l =~ s/[\n\r]//g;
|
||||
$request_uri = "";
|
||||
$post_body = "";
|
||||
%hl = ();
|
||||
|
||||
@c = split( ' ', $l );
|
||||
if( scalar( @c ) > 2 )
|
||||
{
|
||||
$request_uri = $c[1];
|
||||
while( keys( %h ) < 15 ) # Who would ever want to send more headers???
|
||||
while( keys( %h ) < 50 )
|
||||
{
|
||||
$l = <STDIN>;
|
||||
$l =~ s/[\n\r]//g;
|
||||
@@ -59,36 +76,32 @@ if( scalar( @c ) > 2 )
|
||||
$n =~ s/:[^:]*$//g;
|
||||
$v = $l;
|
||||
$v =~ s/^[^:]*://g;
|
||||
$v =~ s/^\s+//;
|
||||
$v =~ s/\s+$//;
|
||||
$h{$n} = $v;
|
||||
$hl{lc( $n )} = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$c[0] = uc( $c[0] );
|
||||
$request_method = $c[0];
|
||||
|
||||
if( $h{Authorization} eq '' )
|
||||
{ error( 401 ); }
|
||||
else
|
||||
if( $request_method eq 'POST' )
|
||||
{
|
||||
@s = split( ' ', $h{Authorization} );
|
||||
if( $s[0] ne 'Basic' or length( $h{Authorization} ) > 80 ) # We can't be too careful, can we...
|
||||
{ error( 401 ); }
|
||||
else
|
||||
my $content_length = 0;
|
||||
|
||||
if( defined( $hl{'content-length'} ) && $hl{'content-length'} =~ /^[0-9]+$/ )
|
||||
{
|
||||
$s[1] =~ tr#A-Za-z0-9+/##cd;
|
||||
$s[1] =~ tr#A-Za-z0-9+/# -_#;
|
||||
$s[1] = pack( 'c', 32 + 0.75 * length( $s[1] ) ) . $s[1];
|
||||
$s[1] = unpack( 'u', $s[1] );
|
||||
$s[1] =~ s/[\r\n]//g;
|
||||
@l = split( ':', $s[1] );
|
||||
if( $l[0] ne 'root' )
|
||||
{ error( 401 ); }
|
||||
else
|
||||
{ if( $x = system( $smart_check_login, @l ) )
|
||||
{ error( 401 ); } }
|
||||
$content_length = int( $hl{'content-length'} );
|
||||
}
|
||||
|
||||
if( $content_length > 0 && $content_length < 8192 )
|
||||
{
|
||||
read( STDIN, $post_body, $content_length );
|
||||
}
|
||||
}
|
||||
|
||||
if( $c[0] ne 'GET' )
|
||||
if( $request_method ne 'GET' && $request_method ne 'POST' )
|
||||
{
|
||||
error( 501 );
|
||||
}
|
||||
@@ -97,19 +110,39 @@ if( $c[0] ne 'GET' )
|
||||
$cc = $c[1];
|
||||
$cc =~ s/[^\?]*\?//;
|
||||
$c = substr( shift( @p ), 1 );
|
||||
@p = split( '&', $p[0] );
|
||||
foreach $p ( @p )
|
||||
{
|
||||
$n = $p;
|
||||
$n =~ s/=.*//;
|
||||
$v = $p;
|
||||
$v =~ s/.*=//;
|
||||
$v =~ s/\+/ /g;
|
||||
$v =~ s/%([0-9A-F][0-9A-F])/pack('c',hex($1))/gie;
|
||||
$p{$n} = $v;
|
||||
}
|
||||
|
||||
parse_params( $p[0] );
|
||||
parse_params( $post_body ) if $request_method eq 'POST';
|
||||
|
||||
@c = split( '/', $c );
|
||||
|
||||
if( $c[0] eq 'login' )
|
||||
{
|
||||
handle_login_route();
|
||||
exit;
|
||||
}
|
||||
|
||||
if( $c[0] eq 'logout' )
|
||||
{
|
||||
handle_logout_route();
|
||||
exit;
|
||||
}
|
||||
|
||||
# Static assets must be available before login, otherwise the login page
|
||||
# cannot load the SMArT logo and icons.
|
||||
if( $c[0] eq 'static' )
|
||||
{
|
||||
do( $smart_libexec_dir . '/static.pl' );
|
||||
handle_request();
|
||||
exit;
|
||||
}
|
||||
|
||||
if( ! valid_session() )
|
||||
{
|
||||
redirect( '/login' );
|
||||
exit;
|
||||
}
|
||||
|
||||
if( ( $c[0] eq 'service' && $c[1] eq 'control' ) ||
|
||||
( $c[0] eq 'cgi-bin' && $c[1] eq 'control' ) )
|
||||
{
|
||||
@@ -176,6 +209,359 @@ exit;
|
||||
##### END OF MAIN PROCEDURES FOLLOW #####
|
||||
##########################################
|
||||
|
||||
sub smart_log_line( $$$ )
|
||||
{
|
||||
my( $level, $file, $msg ) = @_;
|
||||
|
||||
$level = 'INFO' unless defined( $level ) && $level ne '';
|
||||
$file = 'smart' unless defined( $file ) && $file ne '';
|
||||
$msg = '' unless defined( $msg );
|
||||
|
||||
my( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() );
|
||||
my $ts = sprintf( "%04d-%02d-%02d %02d:%02d:%02d",
|
||||
$year + 1900, $mon + 1, $mday, $hour, $min, $sec );
|
||||
|
||||
my $version = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28';
|
||||
|
||||
if( open( my $fh, '>>', $smart_log_path ) )
|
||||
{
|
||||
print( $fh '[' . $ts . '] [' . $level . '] [SMArT ' . $version . '] [' . $file . '] ' . $msg . "\n" );
|
||||
close( $fh );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub smart_auth_log( $ )
|
||||
{
|
||||
my $msg = $_[0];
|
||||
$msg = '' unless defined $msg;
|
||||
|
||||
my( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() );
|
||||
my $ts = sprintf( "%04d-%02d-%02d %02d:%02d:%02d",
|
||||
$year + 1900, $mon + 1, $mday, $hour, $min, $sec );
|
||||
|
||||
my $version = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28';
|
||||
|
||||
if( open( my $fh, '>>', $smart_log_path ) )
|
||||
{
|
||||
print( $fh '[' . $ts . '] [INFO] [SMArT ' . $version . '] [smart] ' . $msg . "\n" );
|
||||
close( $fh );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub parse_params( $ )
|
||||
{
|
||||
my $qs = $_[0];
|
||||
|
||||
return if ! defined( $qs ) || $qs eq '';
|
||||
|
||||
my @items = split( '&', $qs );
|
||||
|
||||
foreach my $item ( @items )
|
||||
{
|
||||
my $n = $item;
|
||||
my $v = $item;
|
||||
|
||||
$n =~ s/=.*//;
|
||||
$v =~ s/^[^=]*=?//;
|
||||
|
||||
$n =~ s/\+/ /g;
|
||||
$v =~ s/\+/ /g;
|
||||
|
||||
$n =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('c',hex($1))/gie;
|
||||
$v =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('c',hex($1))/gie;
|
||||
|
||||
$p{$n} = $v;
|
||||
}
|
||||
}
|
||||
|
||||
sub smart_html_escape( $ )
|
||||
{
|
||||
my $s = $_[0];
|
||||
$s = '' unless defined $s;
|
||||
$s =~ s/&/&/g;
|
||||
$s =~ s/</</g;
|
||||
$s =~ s/>/>/g;
|
||||
$s =~ s/"/"/g;
|
||||
return $s;
|
||||
}
|
||||
|
||||
sub session_timeout()
|
||||
{
|
||||
return $smart_session_timeout if defined( $smart_session_timeout ) && $smart_session_timeout =~ /^[0-9]+$/ && $smart_session_timeout > 0;
|
||||
return 3600;
|
||||
}
|
||||
|
||||
sub session_dir()
|
||||
{
|
||||
my $dir = defined( $smart_session_dir ) && $smart_session_dir ne '' ? $smart_session_dir : '/run/mars-nwe-webui';
|
||||
|
||||
if( ! -d $dir )
|
||||
{
|
||||
if( ! mkdir( $dir, 0700 ) )
|
||||
{
|
||||
smart_auth_log( 'could not create session dir ' . $dir . ': ' . $! );
|
||||
}
|
||||
}
|
||||
|
||||
if( -d $dir )
|
||||
{
|
||||
chmod( 0700, $dir );
|
||||
}
|
||||
else
|
||||
{
|
||||
smart_auth_log( 'session dir is not available: ' . $dir );
|
||||
}
|
||||
|
||||
return $dir;
|
||||
}
|
||||
|
||||
sub session_token()
|
||||
{
|
||||
my $token = '';
|
||||
|
||||
if( open( my $fh, '<', '/dev/urandom' ) )
|
||||
{
|
||||
my $buf = '';
|
||||
read( $fh, $buf, 24 );
|
||||
close( $fh );
|
||||
$token = unpack( 'H*', $buf );
|
||||
}
|
||||
|
||||
if( $token eq '' )
|
||||
{
|
||||
$token = sprintf( "%08x%08x%08x%08x", time(), $$, int( rand( 0xffffffff ) ), int( rand( 0xffffffff ) ) );
|
||||
}
|
||||
|
||||
$token =~ s/[^A-Fa-f0-9]//g;
|
||||
return $token;
|
||||
}
|
||||
|
||||
sub session_file( $ )
|
||||
{
|
||||
my $token = $_[0];
|
||||
$token = '' unless defined $token;
|
||||
$token =~ s/[^A-Fa-f0-9]//g;
|
||||
|
||||
return '' if $token eq '';
|
||||
|
||||
return session_dir() . '/' . $token;
|
||||
}
|
||||
|
||||
sub cookie_session_id()
|
||||
{
|
||||
my $cookie = defined( $hl{'cookie'} ) ? $hl{'cookie'} : '';
|
||||
|
||||
foreach my $part ( split( /;/, $cookie ) )
|
||||
{
|
||||
$part =~ s/^\s+//;
|
||||
$part =~ s/\s+$//;
|
||||
|
||||
if( $part =~ /^SMArT_SID=([A-Fa-f0-9]+)$/ )
|
||||
{
|
||||
return $1;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
sub valid_session()
|
||||
{
|
||||
my $token = cookie_session_id();
|
||||
my $file = session_file( $token );
|
||||
|
||||
return 0 if $token eq '';
|
||||
return 0 if $file eq '';
|
||||
|
||||
if( ! -f $file )
|
||||
{
|
||||
smart_auth_log( 'session cookie exists but file is missing: ' . $file );
|
||||
return 0;
|
||||
}
|
||||
|
||||
my @st = stat( $file );
|
||||
if( scalar( @st ) == 0 )
|
||||
{
|
||||
smart_auth_log( 'could not stat session file: ' . $file );
|
||||
return 0;
|
||||
}
|
||||
|
||||
if( time() - $st[9] > session_timeout() )
|
||||
{
|
||||
unlink( $file );
|
||||
smart_auth_log( 'session expired: ' . $file );
|
||||
return 0;
|
||||
}
|
||||
|
||||
utime( time(), time(), $file );
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub create_session( $ )
|
||||
{
|
||||
my $user = $_[0];
|
||||
my $token = session_token();
|
||||
my $file = session_file( $token );
|
||||
|
||||
if( $file eq '' )
|
||||
{
|
||||
smart_auth_log( 'could not build session file path' );
|
||||
return '';
|
||||
}
|
||||
|
||||
if( open( my $fh, '>', $file ) )
|
||||
{
|
||||
print( $fh $user . "\n" . time() . "\n" );
|
||||
close( $fh );
|
||||
chmod( 0600, $file );
|
||||
smart_auth_log( 'created session for ' . $user . ' at ' . $file );
|
||||
return $token;
|
||||
}
|
||||
|
||||
smart_auth_log( 'could not create session file ' . $file . ': ' . $! );
|
||||
return '';
|
||||
}
|
||||
|
||||
sub destroy_session()
|
||||
{
|
||||
my $token = cookie_session_id();
|
||||
my $file = session_file( $token );
|
||||
|
||||
unlink( $file ) if $file ne '' && -f $file;
|
||||
}
|
||||
|
||||
sub check_login_password( $$ )
|
||||
{
|
||||
my( $user, $pass ) = @_;
|
||||
|
||||
return 0 if ! defined( $user ) || ! defined( $pass );
|
||||
return 0 if $user ne 'root';
|
||||
|
||||
if( ! defined( $smart_check_login ) || $smart_check_login eq '' || ! -x $smart_check_login )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return system( $smart_check_login, $user, $pass ) == 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
sub print_login_page( $ )
|
||||
{
|
||||
my $msg = smart_html_escape( $_[0] );
|
||||
|
||||
print <<EOF;
|
||||
HTTP/1.0 200 OK
|
||||
Content-Type: text/html
|
||||
$server_id
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>SMArT Login</title>
|
||||
<style>
|
||||
:root{--bg:#f4f1ea;--panel:#faf8f4;--line:#dfd2bf;--text:#3d342c;--muted:#6f6257;--accent:#ad1d1c;--gold:#b9813d}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;min-height:100%;background:var(--bg);color:var(--text);font:15px/1.5 Arial,Helvetica,sans-serif}
|
||||
body{display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
.login{width:min(440px,100%);background:var(--panel);border:1px solid var(--line);border-radius:20px;box-shadow:0 18px 45px rgba(64,36,12,.12);overflow:hidden}
|
||||
.hero{padding:26px 28px;background:linear-gradient(135deg,#a80f18,#c44731 60%,#d79a54);color:white}
|
||||
.hero{display:flex;align-items:center;gap:18px}.hero img{width:120px;max-width:34%;height:auto;display:block;background:#fff;border-radius:16px;padding:8px 10px;box-shadow:0 8px 20px rgba(0,0,0,.12)}.hero h1{margin:0;font-size:28px}
|
||||
.hero p{margin:6px 0 0;opacity:.95}
|
||||
form{padding:24px 28px 28px}
|
||||
label{display:block;font-weight:bold;margin:0 0 7px}
|
||||
input{width:100%;border:1px solid #cdbb9f;border-radius:12px;padding:10px 12px;background:#fffdf9;color:var(--text);font-size:15px;margin:0 0 16px}
|
||||
button{width:100%;border:1px solid #a33d2f;border-radius:12px;padding:11px 14px;background:#b84434;color:#fff;font-weight:bold;font-size:15px;cursor:pointer}
|
||||
.msg{margin:0 0 16px;padding:10px 12px;border-radius:12px;background:#fff3e0;border:1px solid #ead0a4;color:#7a3d18}
|
||||
.note{margin-top:14px;color:var(--muted);font-size:13px;text-align:center;line-height:1.45;word-break:break-word}.note a{color:var(--muted);text-decoration:none}.note a:hover{text-decoration:underline}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login">
|
||||
<div class="hero">
|
||||
<img src="/static/smart.jpg" alt="SMArT logo">
|
||||
<div>
|
||||
<h1>SMArT Login</h1>
|
||||
<p>MARS_NWE web administration</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="/login">
|
||||
EOF
|
||||
|
||||
if( $msg ne '' )
|
||||
{
|
||||
print '<div class="msg">' . $msg . "</div>\n";
|
||||
}
|
||||
|
||||
print <<EOF;
|
||||
<label for="user">User</label>
|
||||
<input id="user" name="user" value="root" autocomplete="username">
|
||||
<label for="pass">Password</label>
|
||||
<input id="pass" name="pass" type="password" autocomplete="current-password" autofocus>
|
||||
<button type="submit">Login</button>
|
||||
<div class="note">© Copyright 2026 Mario Fetka<br><a href="mailto:mario.fetka@disconnected-by-peer.at">mario.fetka@disconnected-by-peer.at</a></div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
}
|
||||
|
||||
sub handle_login_route()
|
||||
{
|
||||
if( $request_method ne 'POST' )
|
||||
{
|
||||
print_login_page( '' );
|
||||
return;
|
||||
}
|
||||
|
||||
my $rv = check_login_password( $p{user}, $p{pass} );
|
||||
|
||||
if( $rv == -1 )
|
||||
{
|
||||
print_login_page( 'Login helper check_login is missing or not executable.' );
|
||||
return;
|
||||
}
|
||||
|
||||
if( $rv != 1 )
|
||||
{
|
||||
print_login_page( 'Login failed.' );
|
||||
return;
|
||||
}
|
||||
|
||||
my $token = create_session( $p{user} );
|
||||
|
||||
if( $token eq '' )
|
||||
{
|
||||
print_login_page( 'Could not create login session.' );
|
||||
return;
|
||||
}
|
||||
|
||||
print <<EOF;
|
||||
HTTP/1.0 302 Found
|
||||
Location: /
|
||||
Set-Cookie: SMArT_SID=$token; Path=/; HttpOnly; Max-Age=3600
|
||||
$server_id
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
sub handle_logout_route()
|
||||
{
|
||||
destroy_session();
|
||||
|
||||
print <<EOF;
|
||||
HTTP/1.0 302 Found
|
||||
Location: /login
|
||||
Set-Cookie: SMArT_SID=deleted; Path=/; HttpOnly; Max-Age=0
|
||||
$server_id
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
sub error( $ )
|
||||
{
|
||||
if( $_[0] eq '401' )
|
||||
|
||||
@@ -146,3 +146,7 @@ $nw_cert_file = '@MARS_NWE_INSTALL_FULL_CONFDIR@/server.crt';
|
||||
# TLS private key file in PEM format.
|
||||
# Required only when HTTPS is enabled.
|
||||
$nw_key_file = '@MARS_NWE_INSTALL_FULL_CONFDIR@/server.key';
|
||||
|
||||
# Directory for HTML login cookie sessions. Created by systemd RuntimeDirectory.
|
||||
$smart_session_dir = '/run/mars-nwe-webui';
|
||||
$smart_session_timeout = 3600;
|
||||
|
||||
@@ -19,7 +19,7 @@ body{padding:18px}
|
||||
a{color:inherit} code,tt{font-family:"DejaVu Sans Mono",monospace}
|
||||
.shell{max-width:1250px;margin:0 auto}
|
||||
.hero{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:22px 24px;border:1px solid var(--line);border-radius:18px;background:linear-gradient(135deg,#a80f18,#c44731 60%,#d79a54);color:#fff;box-shadow:0 12px 30px rgba(64,36,12,.08)}
|
||||
.hero img{height:42px;width:auto;display:block;background:#fff;border-radius:10px;padding:5px;box-shadow:0 8px 20px rgba(0,0,0,.12)}
|
||||
.hero-actions{display:flex;align-items:center;gap:12px}.hero img{height:42px;width:auto;display:block;background:#fff;border-radius:10px;padding:5px;box-shadow:0 8px 20px rgba(0,0,0,.12)}.logout-button{display:inline-block;text-decoration:none;border:1px solid rgba(255,255,255,.6);border-radius:10px;padding:8px 13px;background:rgba(255,255,255,.14);color:#fff;font-weight:bold;box-shadow:0 8px 18px rgba(0,0,0,.10)}.logout-button:hover{background:rgba(255,255,255,.22)}
|
||||
.hero h1{margin:0;font-size:28px;line-height:1.1}
|
||||
.hero p{margin:6px 0 0;font-size:15px;opacity:.95}
|
||||
.workspace{margin-top:18px;display:grid;grid-template-columns:180px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@@ -83,18 +83,18 @@ a{color:inherit} code,tt{font-family:"DejaVu Sans Mono",monospace}
|
||||
<h1>Main menu</h1>
|
||||
<p>Choose a section from the icon list. The explanation opens here on the left, and the editor opens on the right.</p>
|
||||
</div>
|
||||
<img src="/static/smart_icon.jpg" alt="SMArT logo">
|
||||
<div class="hero-actions"><a class="logout-button" href="/logout" target="_top">Logout</a><img src="/static/smart_icon.jpg" alt="SMArT logo"></div>
|
||||
</div>
|
||||
|
||||
<div class="workspace">
|
||||
<aside class="nav-shell">
|
||||
<div class="nav-title">Sections</div>
|
||||
<div class="nav-list">
|
||||
<button class="nav-item active" type="button" data-target="setup-first" data-href="/settings/smart" aria-pressed="true" title="Setup first">
|
||||
<button class="nav-item active" type="button" data-target="setup-first" data-href="/settings/smart" aria-pressed="false" title="Setup first">
|
||||
<span class="nav-icon icon-start"><img src="/static/icon-start.svg" alt="" aria-hidden="true"></span>
|
||||
<span class="nav-label">Setup first</span>
|
||||
</button>
|
||||
<button class="nav-item" type="button" data-target="mars-nwe-service" data-href="/static/start.html" aria-pressed="false" title="MARS_NWE service">
|
||||
<button class="nav-item active" type="button" data-target="mars-nwe-service" data-href="/static/start.html" aria-pressed="true" title="MARS_NWE service">
|
||||
<span class="nav-icon icon-service"><img src="/static/icon-service.svg" alt="" aria-hidden="true"></span>
|
||||
<span class="nav-label">MARS_NWE service</span>
|
||||
</button>
|
||||
@@ -626,5 +626,26 @@ default directory.
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function smartDefaultSection(){
|
||||
function openDefault(){
|
||||
if (window.location.hash) return;
|
||||
var btn = document.querySelector('[data-target="mars-nwe-service"]');
|
||||
if (btn && typeof btn.click === 'function') {
|
||||
btn.click();
|
||||
return;
|
||||
}
|
||||
var frame = parent && parent.frames ? parent.frames['OPTS'] : null;
|
||||
if (frame) frame.location = '/static/start.html';
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', openDefault);
|
||||
} else {
|
||||
openDefault();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user