mirror of
https://github.com/webmin/webmin.git
synced 2026-06-05 12:50:23 +01:00
This PR expands the "Bootup and Shutdown" module’s systemd support with creation and management for services, timers, sockets, paths and targets. It also adds user-scoped systemd units, linger controls, status and log actions, tabbed unit listings by type, and contextual help for the new options. For user-scoped units, the implementation includes several safety guards because unit files live under user-controlled home directories: - User accounts are validated with system account data before any user-unit operation is attempted. - User unit names are restricted to known systemd unit suffixes and safe filename characters. - User unit files are limited to direct children of `~/.config/systemd/user`. - Symlinked `.config`, `.config/systemd`, and `.config/systemd/user` paths are rejected. - User unit reads, writes, directory creation, and deletes are performed after dropping privileges to the target Unix user. - File operations re-check paths close to the actual read/write/delete operation to reduce symlink race exposure. - User unit create failures roll back half-created files when daemon reload fails. - User-provided unit names, owners, paths, command output, and logs are HTML-escaped before display. - systemctl, journalctl, and loginctl command arguments are shell-quoted before execution. - User services omit `User=` and `Group=` directives because they already run under the selected user’s systemd manager. Together, all these changes will allow Webmin admin to manage both system and user systemd units while keeping user-controlled home-directory paths from becoming root-level file read/write/delete exploits. Implemented in response to these two issue requests https://github.com/webmin/webmin/issues/2733 and https://github.com/webmin/webmin/issues/2734
673 lines
22 KiB
Perl
Executable File
673 lines
22 KiB
Perl
Executable File
#!/usr/local/bin/perl
|
|
# index.cgi
|
|
# Display a list of run-levels and the actions that are run at boot and
|
|
# shutdown time for each level
|
|
|
|
require './init-lib.pl';
|
|
require './hostconfig-lib.pl';
|
|
&ReadParse();
|
|
&ui_print_header(&text('index_mode', &index_boot_system_title()),
|
|
$text{'index_title'}, "", undef, 1, 1);
|
|
|
|
if ($init_mode eq "osx" && $access{'bootup'}) {
|
|
# This hostconfig if block written by Michael A Peters <mpeters@mac.com>
|
|
# for OSX/Darwin.
|
|
# build hostconfig table
|
|
|
|
@hconf_set = &hostconfig_settings();
|
|
%description_list = &hostconfig_gather(description);
|
|
|
|
print &ui_columns_start([ &text('index_action'),
|
|
&text('index_setting'),
|
|
&text('index_desc') ], 100, 0);
|
|
$i = 0;
|
|
while (<@hconf_set>) {
|
|
$action_description = $description_list{"$hconf_set[$i][0]"};
|
|
print &hostconfig_table($hconf_set[$i][0], $hconf_set[$i][1], $action_description);
|
|
$i++;
|
|
}
|
|
print &ui_columns_end();
|
|
if ($access{'bootup'} == 1) {
|
|
print &ui_links_row([
|
|
&ui_link("edit_hostconfig.cgi?1", $text{'index_add_mac'}),
|
|
&ui_link("edit_hostconfig.cgi?2", &text('index_editconfig',"<tt>$config{'hostconfig'}</tt>") )
|
|
]);
|
|
}
|
|
}
|
|
elsif ($init_mode eq "init" && $access{'bootup'}) {
|
|
# build list of normal and broken actions
|
|
@iacts = &list_actions();
|
|
foreach $a (@iacts) {
|
|
@ac = split(/\s+/, $a);
|
|
$nodemap{$ac[1]} = $ac[0];
|
|
push(@acts, $ac[0]);
|
|
push(@actsl, "0+".&urlize($ac[0]));
|
|
push(@actsf, $ac[0] =~ /^\// ? $ac[0]
|
|
: "$config{'init_dir'}/$ac[0]");
|
|
}
|
|
@runlevels = &list_runlevels();
|
|
foreach $r (@runlevels) {
|
|
foreach $w ("S", "K") {
|
|
foreach $a (&runlevel_actions($r, $w)) {
|
|
@ac = split(/\s+/, $a);
|
|
if (!$nodemap{$ac[2]}) {
|
|
push(@acts, $ac[1]);
|
|
push(@actsl,
|
|
"1+$r+$ac[0]+$ac[1]+$ac[2]+$w");
|
|
push(@actsf, "$config{'init_base'}/rc$r.d/$w$ac[0]$ac[1]");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# For each action, look at /etc/rc*.d/* files to see if it is
|
|
# started at boot
|
|
@boot = &get_inittab_runlevel();
|
|
for($i=0; $i<@acts; $i++) {
|
|
foreach $s (&action_levels('S', $acts[$i])) {
|
|
local ($l, $p) = split(/\s+/, $s);
|
|
local ($lvl) = (&indexof($l, @boot) >= 0);
|
|
push(@{$actsb[$i]}, [ $l, $p, $lvl ]);
|
|
}
|
|
@{$actsb[$i]} = sort { $b->[2] <=> $a->[2] } @{$actsb[$i]};
|
|
}
|
|
|
|
# Sort the actions if necessary
|
|
@order = ( 0 .. $#acts );
|
|
if ($config{'sort_mode'}) {
|
|
@order = sort { local $aa = $actsb[$a]->[0];
|
|
local $bb = $actsb[$b]->[0];
|
|
$bb->[2] <=> $aa->[2] ||
|
|
$bb->[1] <=> $aa->[1] }
|
|
@order;
|
|
}
|
|
@acts = map { $acts[$_] } @order;
|
|
@actsl = map { $actsl[$_] } @order;
|
|
@actsf = map { $actsf[$_] } @order;
|
|
@actsb = map { $actsb[$_] } @order;
|
|
|
|
@links = ( );
|
|
if ($access{'bootup'} == 1) {
|
|
push(@links, &ui_link("edit_action.cgi?2", $text{'index_add'}) );
|
|
}
|
|
if (!$config{'desc'}) {
|
|
# Display actions by name only
|
|
print &ui_links_row(\@links);
|
|
@grid = ( );
|
|
for($i=0; $i<@acts; $i++) {
|
|
if ($acts[$i]) {
|
|
push(@grid, &ui_link("edit_action.cgi?".$actsl[$i], $acts[$i]) );
|
|
}
|
|
}
|
|
print &ui_grid_table(\@grid, 4, 100,
|
|
[ "width=25%", "width=25%", "width=25%", "width=25%" ],
|
|
undef, $text{'index_title'});
|
|
print &ui_links_row(\@links);
|
|
}
|
|
else {
|
|
# Display actions and descriptions
|
|
print &ui_form_start("mass_start_stop.cgi", "post");
|
|
print &ui_links_row(\@links);
|
|
print &ui_columns_start([
|
|
"",
|
|
$text{'index_action'},
|
|
$config{'desc'} == 2 ? $text{'index_levels'}
|
|
: $text{'index_boot'},
|
|
$config{'order'} ? ( $text{'index_order'} ) : ( ),
|
|
$config{'status_check'} == 2 ? ( $text{'index_status'} ) : ( ),
|
|
$text{'index_desc'} ],
|
|
100, 0, [ "", "nowrap", "nowrap", "nowrap", "nowrap" ]);
|
|
|
|
for($i=0; $i<@acts; $i++) {
|
|
local ($boot, %daemon, @levels, $order);
|
|
foreach $s (@{$actsb[$i]}) {
|
|
if ($s->[2]) {
|
|
$boot = 1;
|
|
push(@levels, &ui_text_color($s->[0], 'warn'));
|
|
}
|
|
else {
|
|
push(@levels, $s->[0]);
|
|
}
|
|
}
|
|
$order = $actsb[$i]->[0]->[1];
|
|
local @cols;
|
|
push(@cols, &ui_link("edit_action.cgi?".$actsl[$i], $acts[$i]) );
|
|
local %has;
|
|
$d = &html_escape(&init_description($actsf[$i],
|
|
$config{'status_check'} == 2 ? \%has : undef));
|
|
if ($config{'desc'} == 2) {
|
|
push(@cols, join(" ", @levels));
|
|
}
|
|
else {
|
|
push(@cols,$boot ? &ui_text_color("$text{'yes'}", 'success') :
|
|
&ui_text_color("$text{'no'}", 'warn'));
|
|
}
|
|
if ($config{'order'}) {
|
|
push(@cols, $order);
|
|
}
|
|
if ($config{'status_check'} == 2) {
|
|
if ($actsl[$i] =~ /^0/ && $has{'status'}) {
|
|
local $r = &action_running($actsf[$i]);
|
|
if ($r == 0) {
|
|
push(@cols, &ui_text_color("$text{'no'}", 'warn'));
|
|
}
|
|
elsif ($r == 1) {
|
|
push(@cols, &ui_text_color("$text{'yes'}", 'success'));
|
|
}
|
|
else {
|
|
push(@cols, undef);
|
|
}
|
|
}
|
|
else {
|
|
push(@cols, undef);
|
|
}
|
|
}
|
|
push(@cols, $d);
|
|
if ($actsl[$i] =~ /^0/) {
|
|
print &ui_checked_columns_row(
|
|
\@cols, undef, "idx", $order[$i]);
|
|
}
|
|
else {
|
|
print &ui_columns_row([ undef, @cols ]);
|
|
}
|
|
}
|
|
print &ui_columns_end();
|
|
print &ui_links_row(\@links);
|
|
@buts = ( [ "start", $text{'index_start'} ],
|
|
[ "stop", $text{'index_stop'} ],
|
|
[ "restart", $text{'index_restart'} ] );
|
|
if ($access{'bootup'} == 1) {
|
|
# Show buttons to enable/disable at boot
|
|
push(@buts, undef,
|
|
[ "addboot", $text{'index_addboot'} ],
|
|
[ "delboot", $text{'index_delboot'} ],
|
|
undef,
|
|
[ "addboot_start", $text{'index_addboot_start'} ],
|
|
[ "delboot_stop", $text{'index_delboot_stop'} ],
|
|
);
|
|
}
|
|
print &ui_form_end(\@buts);
|
|
}
|
|
}
|
|
elsif ($init_mode eq "local" && $access{'bootup'} == 1) {
|
|
# Display local bootup script
|
|
if ($config{'hostconfig'}) {
|
|
# This means a darwin system where
|
|
# daemons are not started in the rc script
|
|
print &text('index_script_mac',
|
|
"<tt>$config{'local_script'}</tt>"),"<br>\n";
|
|
}
|
|
else {
|
|
print &text('index_script',
|
|
"<tt>$config{'local_script'}</tt>"),"<br>\n";
|
|
}
|
|
print &ui_form_start("save_local.cgi", "post");
|
|
print &ui_textarea("local",
|
|
&read_file_contents($config{'local_script'}), 15, 80)."<br>\n";
|
|
|
|
# Show shutdown script too, if any
|
|
if ($config{'local_down'}) {
|
|
print &text('index_downscript',
|
|
"<tt>$config{'local_down'}</tt>"),"<br>\n";
|
|
print &ui_textarea("down",
|
|
&read_file_contents($config{'local_down'}), 15, 80).
|
|
"<br>\n";
|
|
}
|
|
|
|
print &ui_form_end([ [ undef, $text{'save'} ] ]);
|
|
}
|
|
elsif ($init_mode eq "win32" && $access{'bootup'}) {
|
|
# Show Windows services
|
|
print &ui_form_start("save_services.cgi", "post");
|
|
print &select_all_link("d"),"\n";
|
|
print &select_invert_link("d"),"<br>\n";
|
|
print &ui_columns_start([ "", $text{'index_sname'},
|
|
$text{'index_sdesc'},
|
|
$text{'index_sboot'},
|
|
$text{'index_sstate'} ]);
|
|
foreach $svc (&list_win32_services()) {
|
|
print &ui_columns_row([
|
|
&ui_checkbox("d", $svc->{'name'}, undef),
|
|
&html_escape($svc->{'name'}),
|
|
&html_escape($svc->{'desc'}),
|
|
$text{'index_sboot'.$svc->{'boot'}} ||
|
|
$svc->{'boot_desc'},
|
|
$text{'index_sstate'.$svc->{'state'}} ||
|
|
$svc->{'state_desc'},
|
|
]);
|
|
}
|
|
print &ui_columns_end();
|
|
print &select_all_link("d"),"\n";
|
|
print &select_invert_link("d"),"<br>\n";
|
|
print &ui_form_end([ [ "start", $text{'index_start'} ],
|
|
[ "stop", $text{'index_stop'} ],
|
|
undef,
|
|
[ "addboot", $text{'index_addboot'} ],
|
|
[ "delboot", $text{'index_delboot'} ],
|
|
undef,
|
|
[ "addboot_start", $text{'index_addboot_start'} ],
|
|
[ "delboot_stop", $text{'index_delboot_stop'} ],
|
|
]);
|
|
print &ui_hr();
|
|
}
|
|
elsif ($init_mode eq "rc" && $access{'bootup'}) {
|
|
# Show FreeBSD scripts
|
|
print &ui_form_start("mass_rcs.cgi", "post");
|
|
@links = ( &select_all_link("d"),
|
|
&select_invert_link("d"),
|
|
&ui_link("edit_rc.cgi?new=1", $text{'index_radd'}) );
|
|
print &ui_links_row(\@links);
|
|
print &ui_columns_start([ "", $text{'index_rname'},
|
|
$text{'index_rdesc'},
|
|
$text{'index_rboot'} ]);
|
|
foreach $rc (&list_rc_scripts()) {
|
|
print &ui_columns_row([
|
|
&ui_checkbox("d", $rc->{'name'}, undef),
|
|
&ui_link("edit_rc.cgi?name=".&urlize($rc->{'name'}),
|
|
&html_escape($rc->{'name'})),
|
|
&html_escape($rc->{'desc'}),
|
|
$rc->{'enabled'} == 1 ? &ui_text_color("$text{'yes'}", 'success') :
|
|
$rc->{'enabled'} == 2 ? "<i>$text{'index_unknown'}</i>":
|
|
&ui_text_color("$text{'no'}", 'warn'),
|
|
]);
|
|
}
|
|
print &ui_columns_end();
|
|
print &ui_links_row(\@links);
|
|
print &ui_form_end([ [ "start", $text{'index_start'} ],
|
|
[ "stop", $text{'index_stop'} ],
|
|
undef,
|
|
[ "addboot", $text{'index_addboot'} ],
|
|
[ "delboot", $text{'index_delboot'} ],
|
|
undef,
|
|
[ "addboot_start", $text{'index_addboot_start'} ],
|
|
[ "delboot_stop", $text{'index_delboot_stop'} ],
|
|
]);
|
|
}
|
|
elsif ($init_mode eq "upstart" && $access{'bootup'}) {
|
|
# Show upstart actions
|
|
print &ui_form_start("mass_upstarts.cgi", "post");
|
|
@links = ( &select_all_link("d"),
|
|
&select_invert_link("d"),
|
|
&ui_link("edit_upstart.cgi?new=1", $text{'index_uadd'}) );
|
|
print &ui_links_row(\@links);
|
|
print &ui_columns_start([ "", $text{'index_uname'},
|
|
$text{'index_udesc'},
|
|
$text{'index_uboot'},
|
|
$text{'index_ustatus'}, ]);
|
|
foreach $u (&list_upstart_services()) {
|
|
if ($u->{'legacy'}) {
|
|
$l = "edit_action.cgi?0+".&urlize($u->{'name'});
|
|
}
|
|
else {
|
|
$l = "edit_upstart.cgi?name=".&urlize($u->{'name'});
|
|
}
|
|
print &ui_columns_row([
|
|
&ui_checkbox("d", $u->{'name'}, undef, 0),
|
|
&ui_link($l, &html_escape($u->{'name'})),
|
|
&html_escape($u->{'desc'}),
|
|
$u->{'boot'} eq 'start' ? &ui_text_color("$text{'yes'}", 'success') :
|
|
$u->{'boot'} eq 'stop' ?
|
|
&ui_text_color("$text{'no'}", 'warn') :
|
|
"<i>$text{'index_unknown'}</i>",
|
|
$u->{'status'} eq 'running' ? &ui_text_color("$text{'yes'}", 'success') :
|
|
$u->{'status'} eq 'waiting' ?
|
|
&ui_text_color("$text{'no'}", 'warn') :
|
|
"<i>$text{'index_unknown'}</i>",
|
|
]);
|
|
}
|
|
print &ui_columns_end();
|
|
print &ui_links_row(\@links);
|
|
print &ui_form_end([ [ "start", $text{'index_start'} ],
|
|
[ "stop", $text{'index_stop'} ],
|
|
[ "restart", $text{'index_restart'} ],
|
|
undef,
|
|
[ "addboot", $text{'index_addboot'} ],
|
|
[ "delboot", $text{'index_delboot'} ],
|
|
undef,
|
|
[ "addboot_start", $text{'index_addboot_start'} ],
|
|
[ "delboot_stop", $text{'index_delboot_stop'} ],
|
|
]);
|
|
|
|
}
|
|
elsif ($init_mode eq "systemd" && $access{'bootup'}) {
|
|
# Show systemd units by type; keep user units on their own tab.
|
|
# Query parameters only choose the tab and optional user context.
|
|
print &systemd_index_style();
|
|
my $scope = $in{'scope'} eq 'user' ? 'user' : '';
|
|
my $unituser = &clean_systemd_unit_value($in{'unituser'});
|
|
my $unituser_details = $unituser ?
|
|
&get_systemd_user_details($unituser) : undef;
|
|
if ($scope eq 'user') {
|
|
$unituser_details || &error($text{'systemd_euser'});
|
|
}
|
|
$unituser = "" if (!$unituser_details);
|
|
my @systemd_units = &list_systemd_services();
|
|
my @user_units = &list_all_systemd_user_services();
|
|
my @tabs = &systemd_index_tabs(\@systemd_units, \@user_units, $unituser);
|
|
my %valid_tabs = map { $_->{'id'}, 1 } @tabs;
|
|
my $requested = defined($in{'mode'}) ? $in{'mode'} : "";
|
|
my $mode = $requested && $valid_tabs{$requested} ? $requested :
|
|
$scope eq 'user' && $valid_tabs{'user'} ? 'user' :
|
|
$tabs[0]->{'id'};
|
|
my $formno = 0;
|
|
|
|
if (@tabs > 1) {
|
|
my @uitabs = map { [ $_->{'id'}, $_->{'title'} ] } @tabs;
|
|
print &ui_tabs_start(\@uitabs, "mode", $mode, 1);
|
|
foreach my $tab (@tabs) {
|
|
print &ui_tabs_start_tab("mode", $tab->{'id'});
|
|
&print_systemd_index_tab($tab, $formno++);
|
|
print &ui_tabs_end_tab("mode", $tab->{'id'});
|
|
}
|
|
print &ui_tabs_end(1);
|
|
}
|
|
else {
|
|
&print_systemd_index_tab($tabs[0], $formno);
|
|
}
|
|
}
|
|
elsif ($init_mode eq "launchd" && $access{'bootup'}) {
|
|
# Show launchd agents
|
|
print &ui_form_start("mass_launchd.cgi", "post");
|
|
@links = ( &select_all_link("d"),
|
|
&select_invert_link("d"),
|
|
&ui_link("edit_launchd.cgi?new=1", $text{'index_ladd'}) );
|
|
print &ui_links_row(\@links);
|
|
print &ui_columns_start([ "", $text{'index_lname'},
|
|
$text{'index_uboot'},
|
|
$text{'index_ustatus'}, ]);
|
|
foreach $u (&list_launchd_agents()) {
|
|
$l = "edit_launchd.cgi?name=".&urlize($u->{'name'});
|
|
print &ui_columns_row([
|
|
&ui_checkbox("d", $u->{'name'}, undef),
|
|
&ui_link($l, $u->{'name'}),
|
|
$u->{'boot'} ? &ui_text_color("$text{'yes'}", 'success') :
|
|
&ui_text_color("$text{'no'}", 'warn'),
|
|
$u->{'status'} ? &ui_text_color("$text{'yes'}", 'success') :
|
|
&ui_text_color("$text{'no'}", 'warn'),
|
|
]);
|
|
}
|
|
print &ui_columns_end();
|
|
print &ui_links_row(\@links);
|
|
print &ui_form_end([ [ "start", $text{'index_start'} ],
|
|
[ "stop", $text{'index_stop'} ],
|
|
[ "restart", $text{'index_restart'} ],
|
|
undef,
|
|
[ "addboot", $text{'index_addboot'} ],
|
|
[ "delboot", $text{'index_delboot'} ],
|
|
undef,
|
|
[ "addboot_start", $text{'index_addboot_start'} ],
|
|
[ "delboot_stop", $text{'index_delboot_stop'} ],
|
|
]);
|
|
|
|
}
|
|
|
|
# reboot/shutdown buttons
|
|
print &ui_hr();
|
|
print &ui_buttons_start();
|
|
if ($init_mode eq 'init' && $access{'bootup'} == 1) {
|
|
print &ui_buttons_row("change_rl.cgi", $text{'index_rlchange'},
|
|
$text{'index_rlchangedesc'}, undef,
|
|
&ui_select("level", $boot[0], \@runlevels));
|
|
}
|
|
if ($access{'reboot'}) {
|
|
print &ui_buttons_row("reboot.cgi", $text{'index_reboot'},
|
|
$text{'index_rebootmsg'});
|
|
}
|
|
if ($access{'shutdown'}) {
|
|
print &ui_buttons_row("shutdown.cgi", $text{'index_shutdown'},
|
|
$text{'index_shutdownmsg'});
|
|
}
|
|
print &ui_buttons_end();
|
|
|
|
&ui_print_footer("/", $text{'index'});
|
|
|
|
# index_boot_system_title()
|
|
# Returns the boot-system label shown in the page header. For systemd
|
|
# systems, prefer the exact systemctl version string so the title reflects
|
|
# what is installed on the host.
|
|
sub index_boot_system_title
|
|
{
|
|
return $text{'mode_'.$init_mode} if ($init_mode ne "systemd");
|
|
my $systemctl = &has_command("systemctl");
|
|
return $text{'mode_systemd'} if (!$systemctl);
|
|
|
|
my $out = &backquote_command(quotemeta($systemctl)." --version 2>/dev/null");
|
|
return $text{'mode_systemd'} if ($?);
|
|
return $text{'mode_systemd'} if (!defined($out) || $out eq "");
|
|
my ($first) = split(/\r?\n/, $out, 2);
|
|
$first = &clean_systemd_unit_value($first);
|
|
return $first || $text{'mode_systemd'};
|
|
}
|
|
|
|
# systemd_index_style()
|
|
# Returns CSS used by the systemd index fragment.
|
|
sub systemd_index_style
|
|
{
|
|
return &ui_tag('style',
|
|
".systemd_linger_toggle { text-decoration: none; }\n".
|
|
".systemd_linger_toggle .ui_text_color { border-bottom: 1px dotted currentColor; }\n",
|
|
{ 'type' => 'text/css' });
|
|
}
|
|
|
|
# systemd_index_tabs(&system-units, &user-units, [user])
|
|
# Builds the tab metadata for the systemd index, keeping system unit types
|
|
# separate and grouping all user-owned units into a dedicated tab.
|
|
sub systemd_index_tabs
|
|
{
|
|
my ($system_units, $user_units, $unituser) = @_;
|
|
my %by_type;
|
|
foreach my $u (@$system_units) {
|
|
my ($display, $type) = &systemd_index_name_type($u->{'name'});
|
|
$type = 'service' if ($u->{'legacy'} || !$type);
|
|
next if (&indexof($type, &get_systemd_list_unit_types()) < 0);
|
|
$u->{'_systemd_display'} = $display;
|
|
$u->{'_systemd_type'} = $type;
|
|
push(@{$by_type{$type}}, $u);
|
|
}
|
|
|
|
my @tabs;
|
|
foreach my $type (&get_systemd_list_unit_types()) {
|
|
my $units = $by_type{$type} || [ ];
|
|
next if (!@$units);
|
|
push(@tabs, { 'id' => $type,
|
|
'type' => $type,
|
|
'title' => &systemd_index_tab_title($type),
|
|
'desc' => &systemd_index_tab_desc($type),
|
|
'units' => $units });
|
|
}
|
|
|
|
foreach my $u (@$user_units) {
|
|
my ($display, $type) = &systemd_index_name_type($u->{'name'});
|
|
$u->{'_systemd_display'} = $display;
|
|
$u->{'_systemd_type'} = $type || 'service';
|
|
}
|
|
if (@$user_units || $unituser) {
|
|
push(@tabs, { 'id' => 'user',
|
|
'user' => 1,
|
|
'unituser' => $unituser,
|
|
'title' => $text{'systemd_tab_user'},
|
|
'desc' => $text{'systemd_tabdesc_user'},
|
|
'units' => $user_units });
|
|
}
|
|
|
|
if (!@tabs) {
|
|
push(@tabs, { 'id' => 'service',
|
|
'type' => 'service',
|
|
'title' => &systemd_index_tab_title('service'),
|
|
'desc' => &systemd_index_tab_desc('service'),
|
|
'units' => [ ] });
|
|
}
|
|
return @tabs;
|
|
}
|
|
|
|
# print_systemd_index_tab(&tab, form-number)
|
|
# Outputs one systemd tab description and its mass-action table.
|
|
sub print_systemd_index_tab
|
|
{
|
|
my ($tab, $formno) = @_;
|
|
my $user_tab = $tab->{'user'} ? 1 : 0;
|
|
my %linger_cache;
|
|
my $create_url = $user_tab && $tab->{'unituser'} ?
|
|
"edit_systemd.cgi?new=1&scope=user&unittype=service&unituser=".
|
|
&urlize($tab->{'unituser'}) :
|
|
$user_tab ? "edit_systemd.cgi?new=1&scope=user&unittype=service" :
|
|
"edit_systemd.cgi?new=1&unittype=".&urlize($tab->{'type'} || 'service');
|
|
my @links = ( &select_all_link("d", $formno),
|
|
&select_invert_link("d", $formno),
|
|
&ui_link($create_url, $text{'index_sadd'}) );
|
|
|
|
print &ui_div($tab->{'desc'});
|
|
print &ui_form_start("mass_systemd.cgi", "post");
|
|
print &ui_links_row(\@links);
|
|
print &ui_hidden("scope", "users") if ($user_tab);
|
|
|
|
my @heads = ( "" );
|
|
push(@heads, $text{'systemd_name'});
|
|
push(@heads, $text{'systemd_desc'}) if ($config{'desc'});
|
|
push(@heads, $text{'systemd_type'}) if ($user_tab);
|
|
push(@heads, $text{'systemd_status'}, $text{'systemd_boot'},
|
|
$text{'index_ustatus'});
|
|
push(@heads, $text{'systemd_owner'}, $text{'systemd_linger_status'})
|
|
if ($user_tab);
|
|
print &ui_columns_start(\@heads);
|
|
foreach my $u (@{$tab->{'units'}}) {
|
|
my $link = $user_tab ?
|
|
"edit_systemd.cgi?scope=user&unituser=".&urlize($u->{'user'}).
|
|
"&name=".&urlize($u->{'name'}) :
|
|
$u->{'legacy'} ? "edit_action.cgi?0+".&urlize($u->{'name'}) :
|
|
"edit_systemd.cgi?name=".&urlize($u->{'name'});
|
|
my $title = (defined($u->{'boot'}) && $u->{'boot'} == -1 ?
|
|
&html_escape($u->{'_systemd_display'}) :
|
|
&ui_link($link, &html_escape($u->{'_systemd_display'})));
|
|
my $checkvalue = $user_tab ?
|
|
&systemd_user_unit_selection_value($u->{'user'}, $u->{'name'}) :
|
|
$u->{'name'};
|
|
my @row = ( &ui_checkbox("d", $checkvalue, undef) );
|
|
push(@row, $title);
|
|
push(@row, &html_escape($u->{'desc'})) if ($config{'desc'});
|
|
push(@row, &html_escape(&systemd_index_unit_type_title(
|
|
$u->{'_systemd_type'}))) if ($user_tab);
|
|
push(@row,
|
|
$u->{'fullstatus'} || &ui_tag('i', $text{'index_unknown'}),
|
|
&systemd_index_boot_column($u),
|
|
&systemd_index_status_column($u));
|
|
if ($user_tab) {
|
|
if (!exists($linger_cache{$u->{'user'}})) {
|
|
$linger_cache{$u->{'user'}} =
|
|
&systemd_user_linger_enabled($u->{'user'});
|
|
}
|
|
push(@row, &ui_tag('tt', &html_escape($u->{'user'})),
|
|
&systemd_linger_toggle_link(
|
|
$u->{'user'}, $linger_cache{$u->{'user'}}));
|
|
}
|
|
print &ui_columns_row(\@row);
|
|
}
|
|
print &ui_columns_end();
|
|
print &ui_links_row(\@links);
|
|
print &ui_form_end([ [ "start", $text{'index_start'} ],
|
|
[ "stop", $text{'index_stop'} ],
|
|
[ "restart", $text{'index_restart'} ],
|
|
undef,
|
|
[ "addboot", $text{'index_addboot'} ],
|
|
[ "delboot", $text{'index_delboot'} ],
|
|
undef,
|
|
[ "addboot_start", $text{'index_addboot_start'} ],
|
|
[ "delboot_stop", $text{'index_delboot_stop'} ],
|
|
]);
|
|
}
|
|
|
|
# systemd_index_name_type(unit-name)
|
|
# Splits a full unit name into display name and unit type.
|
|
sub systemd_index_name_type
|
|
{
|
|
my ($name) = @_;
|
|
my $units_piped = join('|', map { quotemeta } &get_systemd_unit_types());
|
|
my ($type) = $name =~ /\.([^.]+)$/;
|
|
if (defined($type) && $type =~ /^(?:$units_piped)$/) {
|
|
my $display = $name;
|
|
$display =~ s/\.$type$//;
|
|
return ($display, $type);
|
|
}
|
|
return ($name, "");
|
|
}
|
|
|
|
# systemd_index_tab_title(type)
|
|
# Returns the plural tab title for a systemd unit type.
|
|
sub systemd_index_tab_title
|
|
{
|
|
my ($type) = @_;
|
|
return $text{'systemd_tab_'.$type} ||
|
|
$text{'systemd_type_'.$type} ||
|
|
ucfirst($type);
|
|
}
|
|
|
|
# systemd_index_tab_desc(type)
|
|
# Returns the explanatory text shown under a systemd unit tab.
|
|
sub systemd_index_tab_desc
|
|
{
|
|
my ($type) = @_;
|
|
return $text{'systemd_tabdesc_'.$type} || "";
|
|
}
|
|
|
|
# systemd_index_unit_type_title(type)
|
|
# Returns the display label for a single unit type.
|
|
sub systemd_index_unit_type_title
|
|
{
|
|
my ($type) = @_;
|
|
return $text{'systemd_type_'.$type} || $type;
|
|
}
|
|
|
|
# systemd_user_unit_selection_value(user, unit)
|
|
# Encodes a user-unit owner and name into one checkbox value for mass actions.
|
|
sub systemd_user_unit_selection_value
|
|
{
|
|
my ($user, $unit) = @_;
|
|
return &urlize($user)."\t".&urlize($unit);
|
|
}
|
|
|
|
# systemd_linger_toggle_link(user, enabled)
|
|
# Returns a link to toggle linger for a user-unit owner.
|
|
sub systemd_linger_toggle_link
|
|
{
|
|
my ($user, $enabled) = @_;
|
|
my $target = $enabled ? 0 : 1;
|
|
my $label = $enabled ? $text{'yes'} : $text{'no'};
|
|
my $type = $enabled ? 'success' : 'warn';
|
|
my $title = $enabled ? &text('systemd_linger_disable', $user) :
|
|
&text('systemd_linger_enable', $user);
|
|
my $url = "set_systemd_linger.cgi?user=".&urlize($user).
|
|
"&enabled=".$target;
|
|
return &ui_tag('a', &ui_text_color(&html_escape($label), $type),
|
|
{ 'href' => $url,
|
|
'class' => 'systemd_linger_toggle',
|
|
'title' => $title });
|
|
}
|
|
|
|
# systemd_index_boot_column(&unit)
|
|
# Returns the formatted startup-state column for a unit row.
|
|
sub systemd_index_boot_column
|
|
{
|
|
my ($u) = @_;
|
|
return defined($u->{'boot'}) && $u->{'boot'} == 1 ?
|
|
&ui_text_color("$text{'yes'}", 'success') :
|
|
defined($u->{'boot'}) && $u->{'boot'} == 2 ?
|
|
&ui_text_color("$text{'index_sboot6'}", 'success') :
|
|
defined($u->{'boot'}) && $u->{'boot'} == -1 ?
|
|
&ui_text_color("$text{'index_sboot5'}", 'warn') :
|
|
!defined($u->{'boot'}) ?
|
|
&ui_tag('i', $text{'index_unknown'}) :
|
|
&ui_text_color("$text{'no'}", 'warn');
|
|
}
|
|
|
|
# systemd_index_status_column(&unit)
|
|
# Returns the formatted runtime-state column for a unit row.
|
|
sub systemd_index_status_column
|
|
{
|
|
my ($u) = @_;
|
|
return defined($u->{'status'}) && $u->{'status'} == 1 ?
|
|
&ui_text_color("$text{'yes'}", 'success') :
|
|
defined($u->{'status'}) && $u->{'status'} == 0 ?
|
|
&ui_text_color("$text{'no'}", 'warn') :
|
|
&ui_tag('i', $text{'index_unknown'});
|
|
}
|