feat: refresh Smart UI and configuration defaults

This commit is contained in:
Mario Fetka
2026-05-22 07:32:17 +02:00
parent 1394b3ba6c
commit 4ad200c9bb
5 changed files with 1594 additions and 80 deletions

1079
apply.pl

File diff suppressed because it is too large Load Diff

View File

@@ -794,6 +794,423 @@ sub smart_group_checkbox_rows( $ )
}
sub volume_name_from_path( $ )
{
my $path = $_[0];
$path = '' unless defined( $path );
$path =~ s#/*$##;
$path =~ s#^.*/##;
$path = 'ROOT' if $path eq '';
$path = uc( $path );
$path =~ s/[^A-Z0-9_\-]/_/g;
$path = substr( $path, 0, 15 );
$path = 'VOLUME' if $path eq '';
return $path;
}
sub volume_mountpoint_is_useful( $$ )
{
my( $mountpoint, $fstype ) = @_;
return 0 if ! defined( $mountpoint ) || $mountpoint eq '';
return 0 if $mountpoint eq '/';
# Pseudo and system trees are not useful as NetWare volumes.
return 0 if $mountpoint =~ m#^/(proc|sys|dev|run)(/|$)#;
return 0 if $mountpoint =~ m#^/(tmp|var/tmp)(/|$)#;
return 0 if $mountpoint =~ m#^/boot(/|$)#;
return 0 if $mountpoint =~ m#^/(etc|usr|bin|sbin|lib|lib64)(/|$)#;
return 0 if $mountpoint =~ m#^/system(/|$)#;
# Do not require -d here. The webui service may have /home masked by
# systemd hardening, while the real MARS_NWE service can still use it.
return 1;
}
sub local_mountpoints()
{
my @mounts = ();
my %seen = ();
my $add_mount = sub
{
my( $mountpoint, $fstype, $source ) = @_;
$mountpoint = '' unless defined( $mountpoint );
$fstype = '' unless defined( $fstype );
$source = '' unless defined( $source );
$mountpoint =~ s#\\040# #g;
$mountpoint =~ s#\\011#\t#g;
$mountpoint =~ s#\\012#\n#g;
$mountpoint =~ s#\\134#\\#g;
return if $mountpoint eq '';
return if $mountpoint =~ /[\r\n\t]/;
return if $mountpoint =~ /'/;
return if $seen{$mountpoint};
return if $fstype =~ /^(proc|sysfs|devtmpfs|devpts|tmpfs|securityfs|cgroup|cgroup2|pstore|bpf|tracefs|debugfs|configfs|fusectl|mqueue|hugetlbfs|autofs|overlay|squashfs|portal|binfmt_misc|efivarfs)$/;
return if ! volume_mountpoint_is_useful( $mountpoint, $fstype );
push( @mounts, {
path => $mountpoint,
fstype => $fstype ne '' ? $fstype : 'mount',
source => $source ne '' ? $source : 'mount',
name => volume_name_from_path( $mountpoint ),
} );
$seen{$mountpoint} = 1;
};
foreach my $file ( '/proc/1/mountinfo', '/proc/self/mountinfo' )
{
next if ! open( my $fh, '<', $file );
while( my $line = <$fh> )
{
chomp( $line );
my @parts = split( / - /, $line, 2 );
next if scalar( @parts ) != 2;
my @left = split( /\s+/, $parts[0] );
my @right = split( /\s+/, $parts[1] );
next if scalar( @left ) < 5;
next if scalar( @right ) < 3;
$add_mount->( $left[4], $right[0], $right[1] );
}
close( $fh );
}
if( open( my $fh, '<', '/proc/mounts' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
my( $source, $mountpoint, $fstype ) = split( /\s+/, $line );
$add_mount->( $mountpoint, $fstype, $source );
}
close( $fh );
}
@mounts = sort { $a->{path} cmp $b->{path} } @mounts;
return @mounts;
}
sub volume_mount_import_rows()
{
my $html = '';
my @mounts = local_mountpoints();
my %existing = ();
foreach my $line ( getconfig( 1 ) )
{
my @f = split( ' ', $line );
$existing{lc( $f[1] )} = 1 if defined( $f[1] ) && $f[1] ne '';
$existing{'path:' . $f[2]} = 1 if defined( $f[2] ) && $f[2] ne '';
}
if( scalar( @mounts ) == 0 )
{
return qq|\t<TR BGCOLOR="#ece0cf">\n\t\t<TD COLSPAN=2><B>Local mountpoints detected on host</B></TD>\n\t</TR>\n\t<TR BGCOLOR="#fbf7f1">\n\t\t<TD COLSPAN=2><SMALL>No suitable local mountpoints were found.</SMALL></TD>\n\t</TR>\n|;
}
$html .= qq|\t<TR BGCOLOR="#ece0cf">\n\t\t<TD COLSPAN=2><B>Local mountpoints detected on host</B></TD>\n\t</TR>\n|;
foreach my $m ( @mounts )
{
my $name = $m->{name};
my $path = $m->{path};
my $fstype = $m->{fstype};
my $source = $m->{source};
my $exists = $existing{lc( $name )} || $existing{'path:' . $path};
$html .= "\t<TR BGCOLOR=\"#fbf7f1\">\n";
$html .= "\t\t<TD><TT>" . html_escape( $path ) . "</TT><BR><SMALL>Volume: <TT>" . html_escape( $name ) . "</TT> &nbsp; Type: <TT>" . html_escape( $fstype ) . "</TT> &nbsp; Source: <TT>" . html_escape( $source ) . "</TT></SMALL></TD>\n";
if( $exists )
{
$html .= "\t\t<TD ALIGN=RIGHT><SMALL>already configured</SMALL></TD>\n";
}
else
{
my $href = '/settings/volumes/add_new?mount_path=' . url_escape( $path ) . '&path=' . url_escape( $path );
$html .= "\t\t<TD ALIGN=RIGHT><A HREF=\"" . html_escape( $href ) . "\">Add as volume</A></TD>\n";
}
$html .= "\t</TR>\n";
}
return $html;
}
sub volume_mount_defaults_from_query()
{
my %defaults = ();
$defaults{name} = '';
$defaults{path} = '';
my $path = '';
if( defined( $p{mount_path} ) && $p{mount_path} ne '' )
{
$path = $p{mount_path};
}
elsif( defined( $p{path} ) && $p{path} ne '' )
{
$path = $p{path};
}
if( $path ne '' )
{
$path =~ s/[\r\n\t']//g;
$path =~ s#/{2,}#/#g;
$path =~ s#/$## if length( $path ) > 1;
if( volume_mountpoint_is_useful( $path, 'mount' ) )
{
$defaults{path} = $path;
$defaults{name} = volume_name_from_path( $path );
}
}
return %defaults;
}
sub mars_nwe_service_active()
{
my $service = defined( $mars_nwe_service ) && $mars_nwe_service ne '' ? $mars_nwe_service : 'mars-nwe-serv.service';
my $systemctl = defined( $smart_systemctl_path ) && $smart_systemctl_path ne '' ? $smart_systemctl_path : '/usr/bin/systemctl';
return 0 if ! -x $systemctl;
my $rc = system( $systemctl, 'is-active', '--quiet', $service );
return $rc == 0 ? 1 : 0;
}
sub mars_nwe_service_guard_rows( $ )
{
my $what = $_[0];
$what = 'this section' unless defined( $what ) && $what ne '';
return '' if mars_nwe_service_active();
return qq|\t<TR BGCOLOR="#fff3e0">\n|
. qq|\t\t<TD COLSPAN=2><B>MARS_NWE service is not running.</B><BR>\n|
. qq|\t\t<SMALL>Start <TT>mars-nwe-serv.service</TT> before managing | . html_escape( $what ) . qq|. |
. qq|<A HREF="/static/start.html" TARGET="OPTS">Open service page</A></SMALL></TD>\n|
. qq|\t</TR>\n|;
}
sub settings_message_html()
{
return '' if ! defined( $p{msg} ) || $p{msg} eq '';
my $msg = $p{msg};
$msg =~ s/\+/ /g;
return qq|<DIV CLASS="smart-message">| . html_escape( $msg ) . qq|</DIV>\n|;
}
sub user_group_checkbox_rows( $ )
{
my $user = $_[0];
my $html = '';
my %selected = ();
$user = '' unless defined( $user );
if( $user ne '' && $user ne 'add_new' )
{
my $server = get_server();
my $groups = `nwbpvalues -S $server -t 1 -o $user -p "GROUPS_I'M_IN" 2>/dev/null`;
foreach my $line ( split( /\n/, $groups ) )
{
if( $line =~ /([A-Za-z0-9_\-]+)/ )
{
$selected{uc( $1 )} = 1;
}
}
}
my @groups = getconfig( 12 );
my @names = ();
foreach my $g ( @groups )
{
my @f = split( /\s+/, $g );
next if ! defined( $f[1] ) || $f[1] eq '';
push( @names, uc( $f[1] ) );
}
push( @names, 'EVERYONE' ) if ! grep { $_ eq 'EVERYONE' } @names;
@names = sort @names;
foreach my $g ( @names )
{
my $checked = ( $selected{$g} || ( $g eq 'EVERYONE' && $user eq 'add_new' ) ) ? ' CHECKED' : '';
$html .= qq|<LABEL STYLE="margin-left:10px;white-space:nowrap;">| . html_escape( $g )
. qq| <INPUT NAME="group_| . html_escape( $g ) . qq|" TYPE=CHECKBOX$checked></LABEL>\n|;
}
return $html;
}
sub current_bindery_unix_user( $ )
{
# Do not query the live bindery while rendering the edit form.
# Some nwbpvalues versions can block or print unexpected data here.
# Existing UNIX_USER stays unchanged unless the admin selects a value.
return '';
}
sub smart_unix_users_for_select()
{
my @users = ();
my %seen = ();
my $helper = '/usr/lib64/mars_nwe/smart_userlist';
if( -x $helper && open( my $fh, '-|', $helper ) )
{
while( my $line = <$fh> )
{
chomp( $line );
my( $name, $uid, $gid, $gecos, $home, $shell ) = split( /\t/, $line );
next if ! defined( $name ) || $name eq '';
next if $seen{$name};
push( @users, {
name => $name,
uid => defined( $uid ) ? $uid : '',
fullname => defined( $gecos ) ? $gecos : '',
} );
$seen{$name} = 1;
}
close( $fh );
}
# Fallback: getent passwd. This is only used when smart_userlist is not
# available or returned nothing.
if( scalar( @users ) == 0 && open( my $fh, '-|', 'getent', 'passwd' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
my( $name, undef, $uid, $gid, $gecos, $home, $shell ) = split( /:/, $line );
next if ! defined( $name ) || $name eq '';
next if defined( $uid ) && $uid ne '' && $uid < 1000;
next if defined( $shell ) && $shell =~ m#/(false|nologin)$#;
next if $seen{$name};
push( @users, {
name => $name,
uid => defined( $uid ) ? $uid : '',
fullname => defined( $gecos ) ? $gecos : '',
} );
$seen{$name} = 1;
}
close( $fh );
}
@users = sort { $a->{name} cmp $b->{name} } @users;
return @users;
}
sub unix_user_select_options( $ )
{
my $selected = $_[0];
my $html = '';
$selected = '' unless defined( $selected );
foreach my $u ( smart_unix_users_for_select() )
{
next if ! defined( $u ) || ref( $u ) ne 'HASH';
my $name = $u->{name};
next if ! defined( $name ) || $name eq '';
my $sel = $name eq $selected ? ' SELECTED' : '';
$html .= '<OPTION VALUE="' . html_escape( $name ) . '"' . $sel . '>' . html_escape( $name ) . "</OPTION>
";
}
if( $selected ne '' && $html !~ /VALUE="\Q$selected\E"/ )
{
$html = '<OPTION VALUE="' . html_escape( $selected ) . '" SELECTED>' . html_escape( $selected ) . "</OPTION>\n" . $html;
}
return $html;
}
sub user_unix_mapping_row( $$ )
{
my( $user, $selected ) = @_;
$user = '' unless defined( $user );
$selected = '' unless defined( $selected );
my $unix_user_options = unix_user_select_options( $selected );
if( $user eq 'add_new' )
{
return qq| <TR BGCOLOR="#fbf7f1">\n|
. qq|\t\t<TD><B>UNIX user:</B></TD>\n|
. qq|\t\t<TD ALIGN=RIGHT>\n|
. qq|\t\t\t<SELECT NAME="unix_user" SIZE=5 STYLE="width:200px;max-width:200px;">\n|
. qq|\t\t\t\t<OPTION VALUE=""></OPTION>\n|
. $unix_user_options
. qq|\t\t\t</SELECT><BR>\n|
. qq|\t\t</TD>\n|
. qq|\t</TR>\n|;
}
my $display = $selected ne '' ? $selected : '(unchanged)';
return qq| <TR BGCOLOR="#fbf7f1">\n|
. qq|\t\t<TD><B>UNIX user:</B></TD>\n|
. qq|\t\t<TD ALIGN=RIGHT><TT>| . html_escape( $display ) . qq|</TT><BR>\n|
. qq|\t\t\t<LABEL><INPUT TYPE=CHECKBOX NAME="change_unix_user" VALUE="1" onclick="document.getElementById('unix_user_change_box').style.display=this.checked?'block':'none'"> Change UNIX user mapping</LABEL>\n|
. qq|\t\t\t<DIV ID="unix_user_change_box" STYLE="display:none;margin-top:8px;">\n|
. qq|\t\t\t\t<SELECT NAME="unix_user" SIZE=5 STYLE="width:200px;max-width:200px;">\n|
. qq|\t\t\t\t\t<OPTION VALUE=""></OPTION>\n|
. $unix_user_options
. qq|\t\t\t\t</SELECT>\n|
. qq|\t\t\t</DIV>\n|
. qq|\t\t</TD>\n|
. qq|\t</TR>\n|;
}
sub handle_request()
{
if( $c[1] eq 'general' )
@@ -849,12 +1266,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/general" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/general" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -1008,12 +1426,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/dirs" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/dirs" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -1137,12 +1556,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/configh" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/configh" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -1271,12 +1691,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/security" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/security" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -1394,12 +1815,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/susers" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/susers" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -1561,12 +1983,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/logging" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/logging" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -1768,6 +2191,7 @@ EOF
}
elsif( $c[1] eq 'volumes' )
{
my $volume_mount_import_rows = volume_mount_import_rows();
if( $c[2] eq '' )
{
print <<EOF;
@@ -1803,12 +2227,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
@{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
<B><FONT SIZE=+2>Volumes</FONT></B>
@@ -1836,6 +2261,7 @@ EOF
print <<EOF;
<TR BGCOLOR="#d7c0a0">
<TD COLSPAN=2>
$volume_mount_import_rows
<B><A HREF="/settings/volumes/add_new">Add new volume</A></B>
</TD>
</TR>
@@ -1848,9 +2274,36 @@ EOF
}
else
{
$c = getconfigline( '1 ' . $c[2] );
my $volume_action = $c[2];
$c = getconfigline( '1 ' . $volume_action );
@c = split( ' ', $c );
$c = '';
my %volume_mount_defaults = volume_mount_defaults_from_query();
if( $volume_action eq 'add_new' )
{
$c[0] = $volume_mount_defaults{name} if defined( $volume_mount_defaults{name} ) && $volume_mount_defaults{name} ne '';
$c[1] = $volume_mount_defaults{path} if defined( $volume_mount_defaults{path} ) && $volume_mount_defaults{path} ne '';
$c[2] = 'k' if ! defined( $c[2] ) || $c[2] eq '';
}
if( $volume_action eq 'add_new' )
{
my %volume_mount_defaults = volume_mount_defaults_from_query();
if( defined( $volume_mount_defaults{name} ) && $volume_mount_defaults{name} ne '' )
{
$c[0] = $volume_mount_defaults{name};
}
if( defined( $volume_mount_defaults{path} ) && $volume_mount_defaults{path} ne '' )
{
$c[1] = $volume_mount_defaults{path};
}
# Default new imported volumes to lower-case filenames.
$c[2] = 'k' if ! defined( $c[2] ) || $c[2] eq '';
}
$d = $c[2];
$d =~ s/[^ik]//g;
@@ -1928,12 +2381,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/volumes/$c[0]" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/volumes/$c[0]" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -2100,12 +2554,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
@{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
<B><FONT SIZE=+2>Devices</FONT></B>
@@ -2216,12 +2671,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/devices/$title" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/devices/$title" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -2327,12 +2783,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/smart" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/smart" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -2441,12 +2898,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
@{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
<B><FONT SIZE=+2>Users</FONT></B>
@@ -2553,6 +3011,9 @@ EOF
}
}
my $user_group_checkbox_rows = user_group_checkbox_rows( $c[2] );
my $current_unix_user = defined( $p{unix_user} ) ? $p{unix_user} : '';
my $user_unix_mapping_row = user_unix_mapping_row( $c[2], $current_unix_user );
print <<EOF;
HTTP/1.0 200 OK
Content-Type: text/html
@@ -2586,11 +3047,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
@{[ settings_message_html() ]}
<FORM ACTION="/apply/users/$c[2]" METHOD=GET>
<INPUT TYPE=HIDDEN NAME="unix_user_import_default" VALUE="$default_unix_user">
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
@@ -2635,21 +3098,14 @@ EOF
<INPUT NAME="password" TYPE=PASSWORD SIZE=20><BR>
</TD>
</TR>
<TR BGCOLOR="#fbf7f1">
<TD>
<B>UNIX user:</B>
</TD>
<TD ALIGN=RIGHT>
<SELECT NAME="unix_user" SIZE=5>
$unix_user_list </SELECT>
</TD>
</TR>
<TR BGCOLOR="#fbf7f1">
$user_unix_mapping_row
<TR BGCOLOR="#fbf7f1">
<TD>
<B>Groups belonged to:</B>
</TD>
<TD ALIGN=RIGHT>
$group_list </TD>
$user_group_checkbox_rows
</TD>
</TR>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -2685,6 +3141,7 @@ EOF
}
elsif( $c[1] eq 'groups' )
{
my $groups_service_guard_rows = mars_nwe_service_guard_rows( 'groups' );
$server = get_server();
if( $c[2] eq '' )
@@ -2722,12 +3179,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
@{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
<B><FONT SIZE=+2>Groups</FONT></B>
@@ -2830,12 +3288,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/groups/$c[2]" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/groups/$c[2]" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -2895,6 +3354,7 @@ EOF
}
elsif( $c[1] eq 'queues' )
{
my $queues_service_guard_rows = mars_nwe_service_guard_rows( 'print queues' );
$server = get_server();
if( $c[2] eq '' )
@@ -2932,12 +3392,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
@{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
<B><FONT SIZE=+2>Print queues</FONT></B>
@@ -3028,6 +3489,7 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
}
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
<SCRIPT TYPE="text/javascript">
var smartCupsCommands = {};
@@ -3053,7 +3515,7 @@ window.onload = function() {
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/queues/$c[2]" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/queues/$c[2]" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">
<TD>
@@ -3202,12 +3664,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { border:1px solid #a3
A { color:#9f2f26; }
TT { color:#5b4b38; }
.smallnote { color:#6f6257; font-size:12px; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style>
</HEAD>
<BODY BGCOLOR="#f6f2ea">
$settings_nav_bar
<FORM ACTION="/apply/advanced" METHOD=GET>
@{[ settings_message_html() ]}<FORM ACTION="/apply/advanced" METHOD=GET>
$advanced_hidden
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0">

View File

@@ -475,7 +475,7 @@ 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}
.note{margin-top:14px;color:var(--muted);font-size:13px;text-align:center;line-height:1.45}
</style>
</head>
<body>
@@ -501,7 +501,7 @@ EOF
<label for="pass">Password</label>
<input id="pass" name="pass" type="password" autocomplete="current-password" autofocus>
<button type="submit">Login</button>
<div class="note">&copy; Copyright 2026 Mario Fetka<br><a href="mailto:mario.fetka@disconnected-by-peer.at">mario.fetka@disconnected-by-peer.at</a></div>
<div class="note">&copy; Copyright 2026 Mario Fetka</div>
</form>
</div>
</body>

View File

@@ -150,3 +150,30 @@ $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;
# SMArT Perl logging verbosity.
#
# This controls log messages written by the Perl CGI-style helper scripts
# such as apply.pl. The messages are written to the SMArT log file configured
# for the WebUI, normally:
#
# /var/log/mars_nwe/smart.log
#
# Supported values, from quiet to verbose:
#
# error - only real errors that abort or fail an operation
# warning - errors and warnings about unusual but non-fatal situations
# info - normal operational messages, command start/finish, default
# debug - additional diagnostic information for troubleshooting
# trace - very verbose step-by-step traces, including bindery pipe payloads
#
# Recommended setting for normal operation:
#
# $smart_debug_level = 'info';
#
# Use 'trace' only while debugging a concrete problem. Trace logging may
# include submitted bindery payload data and can produce a lot of log output.
# After debugging, switch back to 'info'.
$smart_debug_level = 'info';

View File

@@ -73,7 +73,11 @@
font-size: 13px;
color: #6c5b52;
}
</STYLE>
.runtime-info{margin:18px 12px 0;border:1px solid #ddcfba;border-radius:16px;overflow:hidden;background:#fbf7f1;box-shadow:0 6px 18px rgba(80,55,30,.06)}.runtime-info-title{background:#d7c0a0;padding:10px 14px;font-weight:bold;color:#2e261d}.runtime-info-row{display:grid;grid-template-columns:190px minmax(0,1fr);gap:12px;padding:8px 14px;border-top:1px solid #eadfce;align-items:center}.runtime-info-row code{color:#5b4b38;white-space:normal;word-break:break-word}.runtime-info-note{padding:10px 14px;border-top:1px solid #eadfce;color:#6b5b50;font-size:13px;line-height:1.35}
.project-footer{margin:16px auto 0;text-align:center;color:#6b5b50;font-size:14px;line-height:1.45;max-width:760px}.project-footer a{color:#9f2f26;text-decoration:none}.project-footer a:hover{text-decoration:underline}.copyright{margin:10px auto 0;text-align:center;color:#6b5b50;font-size:13px;max-width:760px}.copyright a{color:#9f2f26;text-decoration:none}.copyright a:hover{text-decoration:underline}
</STYLE>
</HEAD>
<BODY>
<DIV CLASS="wrapper">
@@ -105,13 +109,42 @@
</TR>
<TR>
<TD WIDTH="50%"><A CLASS="action secondary" HREF="/service/control?restart">Restart <TT>MARS_NWE</TT></A></TD>
<TD WIDTH="50%"><A CLASS="action secondary" HREF="/service/control?status">Status <TT>MARS_NWE</TT></A></TD>
<TD WIDTH="50%"><A CLASS="action secondary" HREF="/service/control?status">Status <TT>MARS_NWE</TT></A>
</TD>
</TR>
</TABLE>
</DIV>
<DIV CLASS="footer">
The newest version of SMArT can be downloaded from <A HREF="http://www.lintux.cx/" TARGET="_parent">the project website</A>.<BR><BR>
&copy; Copyright 2026 <A HREF="mailto:mario.fetka@disconnected-by-peer.at">Mario Fetka</A>
<DIV CLASS="runtime-info">
<DIV CLASS="runtime-info-title">MARS_NWE runtime information</DIV>
<DIV CLASS="runtime-info-row">
<DIV>Configuration file</DIV>
<DIV><CODE>@MARS_NWE_INSTALL_FULL_CONFDIR@/nwserv.conf</CODE></DIV>
</DIV>
<DIV CLASS="runtime-info-row">
<DIV>SMArT configuration</DIV>
<DIV><CODE>@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf</CODE></DIV>
</DIV>
<DIV CLASS="runtime-info-row">
<DIV>WebUI scripts</DIV>
<DIV><CODE>@MARS_NWE_INSTALL_FULL_LIBEXECDIR@</CODE></DIV>
</DIV>
<DIV CLASS="runtime-info-row">
<DIV>MARS_NWE service</DIV>
<DIV><CODE>mars-nwe-serv.service</CODE></DIV>
</DIV>
<DIV CLASS="runtime-info-note">
User, group and queue operations need a running <B>MARS_NWE</B> service and a reachable bindery.
Configuration changes require write access to the SMArT configuration directory.
</DIV>
</DIV>
<DIV CLASS="footer">
<DIV CLASS="project-footer">
<DIV>SMArT is shipped as part of the <B>MARS_NWE</B> package.</DIV>
<DIV>Project sources are available from the <A HREF="https://gitea.disconnected-by-peer.at/mars_nwe/mars-nwe" TARGET="_blank">MARS_NWE repository</A>.</DIV>
</DIV><BR><BR>
<DIV CLASS="copyright">&copy; Copyright 2026 <A HREF="mailto:mario.fetka@disconnected-by-peer.at">Mario Fetka</A></DIV>
</DIV>
</DIV>
</DIV>