\n". &ui_submit($text{'index_srefresh'}). &ui_form_end(), }); } # Suggest to GPL user to get Virtualmin Pro if (&should_show_pro_tip('dashboard', 1)) { # Do not show Pro advertisement on the dashboard within first three days my $goodtime; my $lastpost = $config{'lastpost'}; if ($lastpost) { # Use last update time instead, i.e. don't nag potentially right # after first installation $goodtime = (time() - $lastpost > 60*60*24 * 3); } else { # Use system uptime as a fallback &foreign_require("webmin"); my $uptime = &webmin::get_system_uptime(); $goodtime = (!$uptime || $uptime > 60*60*24 * 3); } # Show alert in another 21 days if reminder been set my ($remind) = &should_show_pro_tip('dashboard_reminder', 1); my $futureremind = (int($remind) + (60*60*24 * 21)); my $doremind = ($remind && $futureremind < time()); if (($goodtime && !$remind) || ($goodtime && $doremind)) { push(@rv, { 'type' => 'warning', 'level' => 'info', 'warning' => { 'alert' => &alert_pro_tip('dashboard', { 'alert_title' => $text{'scripts_gpl_pro_tip_title_dashboard'}, 'alert_body1' => $text{'scripts_gpl_pro_tip_dashboard'} . " ", 'alert_body2' => &text('scripts_gpl_pro_tip_enroll_dashboard', $virtualmin_docs_pro), 'button_text' => $text{'scripts_gpl_pro_tip_hide2'}, 'button_icon' => 'fa fa-fw fa-heartbeat', 'button_text2' => $text{'scripts_gpl_pro_tip_open'}, 'button_icon2' => 'fa fa-fw fa-unlock', 'button_text3' => $text{'scripts_gpl_pro_tip_remind'}, 'button_icon3' => 'fa fa-fw fa-clock', }) }, }); } } # Tell GPL users about WP Workbench if (&should_show_pro_tip('wp_workbench_ad', 1)) { # Show WP Workbench notice on the dashboard my $goodtime = 1; # Show alert in another 21 days if reminder been set my ($remind) = &should_show_pro_tip('wp_workbench_ad_reminder', 1); my $futureremind = (int($remind) + (60*60*24 * 21)); my $doremind = ($remind && $futureremind < time()); my $target_link = "https://www.virtualmin.com/docs/plugins/wp-workbench/"; my $pref = 'scripts_gpl_pro_tip_'; push(@rv, { 'type' => 'warning', 'level' => 'info', 'warning' => { 'alert' => &alert_pro_tip('wp_workbench_ad', { 'main_icon' => ' fa-exclamation-circle inline-text', 'alert_title' => $text{"${pref}plugin_dashboard"}, 'alert_body1' => $text{"${pref}workbench_desc_dashboard"}, 'alert_body2' => " ", 'target_link' => $target_link, 'button_text2' => $text{"${pref}learn_more"}, 'button_icon2' => 'fa fa-fw fa-question-circle', 'button_text3' => $text{"${pref}remind"}, 'button_icon3' => 'fa fa-fw fa-clock', 'button_text' => $text{"${pref}hide"}, 'button_icon' => 'fa fa-fw fa-times-circle-o', }) }, }) if (($goodtime && !$remind) || ($goodtime && $doremind)); } # Show a domain owner info about his domain, but NOT info about the system if (!&master_admin() && !&reseller_admin()) { my @table; # General info about the domain my $ex = &extra_admin(); my $d = $ex ? &get_domain($ex) : &get_domain_by("user", $remote_user, "parent", ""); push(@table, { 'desc' => $text{'right_login'}, 'value' => $remote_user }); push(@table, { 'desc' => $text{'right_from'}, 'value' => $ENV{'REMOTE_HOST'} }); push(@table, { 'desc' => $text{'right_virtualmin'}, 'value' => $module_info{'version'} }); push(@table, { 'desc' => $text{'right_dom'}, 'value' => &show_domain_name($d) }); # Number of sub-servers my @subs = ( $d, virtual_server::get_domain_by("parent", $d->{'id'}) ); my @reals = grep { !$_->{'alias'} } @subs; my @mails = grep { $_->{'mail'} } @subs; my ($sleft, $sreason, $stotal, $shide) = &count_domains("realdoms"); if ($sleft < 0 || $shide) { push(@table, { 'desc' => $text{'right_subs'}, 'value' => scalar(@reals) }); } else { push(@table, { 'desc' => $text{'right_subs'}, 'value' => &text('right_of', scalar(@reals), $stotal) }); } # Number of alias domains my @aliases = grep { $_->{'alias'} } @subs; if (@aliases) { my ($aleft, $areason, $atotal, $ahide) = &count_domains("aliasdoms"); if ($aleft < 0 || $ahide) { push(@table, { 'desc' => $text{'right_aliases'}, 'value' => scalar(@aliases) }); } else { push(@table, { 'desc' => $text{'right_aliases'}, 'value' => &text('right_of', scalar(@aliases), $atotal) }); } } # Users and aliases my $users = &count_domain_feature("mailboxes", @subs); my ($uleft, $ureason, $utotal, $uhide) = &count_feature("mailboxes"); my $msg = @mails ? $text{'right_fusers'} : $text{'right_fusers2'}; if ($uleft < 0 || $uhide) { push(@table, { 'desc' => $msg, 'value' => $users }); } else { push(@table, { 'desc' => $msg, 'value' => &text('right_of', $users, $utotal) }); } # Mail aliases if (@mails) { my $aliases = &count_domain_feature("aliases", @subs); my ($aleft, $areason, $atotal, $ahide) = virtual_server::count_feature("aliases"); if ($aleft < 0 || $ahide) { push(@table, { 'desc' => $text{'right_faliases'}, 'value' => $aliases }); } else { push(@table, { 'desc' => $text{'right_faliases'}, 'value' => &text('right_of', $aliases, $atotal) }); } } # Database count my $dbs = &count_domain_feature("dbs", @subs); my ($dleft, $dreason, $dtotal, $dhide) = virtual_server::count_feature("dbs"); if ($dleft < 0 || $dhide) { push(@table, { 'desc' => $text{'right_fdbs'}, 'value' => $dbs }); } else { push(@table, { 'desc' => $text{'right_fdbs'}, 'value' => &text('right_of', $dbs, $dtotal) }); } # Quota summary for top-level domain if (!$data->{'noquotas'} && virtual_server::has_home_quotas()) { my $homesize = virtual_server::quota_bsize("home"); my $mailsize = virtual_server::quota_bsize("mail"); my ($home, $mail, $db) = &get_domain_quota($d, 1); my $usage = $home*$homesize + $mail*$mailsize + $db; my $limit = $d->{'quota'}*$homesize; if ($limit) { if ($usage > $limit) { $limit = $usage; } push(@table, { 'desc' => $text{'right_quota'}, 'value' => &text('right_out', &nice_size($usage), &nice_size($limit)), 'chart' => [ $limit, $usage-$db, $db ]}); } else { push(@table, { 'desc' => $text{'right_quota'}, 'value' => &nice_size($usage), 'wide' => 1 }); } } push(@rv, { 'type' => 'table', 'id' => 'domain', 'desc' => $text{'right_header3'}, 'table' => \@table }); if ($data->{'nosysinfo'}) { push(@rv, { 'type' => 'veto', 'veto' => 'sysinfo' }); } } # If this user can't see system info, block it if (!&can_view_sysinfo()) { push(@rv, { 'type' => 'veto', 'veto' => 'sysinfo' }); } # Virtualmin package updates, filtered from the possible updates list my $hasvposs = foreign_check("package-updates"); my $canvposs = foreign_available("package-updates"); if (!$data->{'noupdates'} && $hasvposs && $canvposs && @vposs) { my $html = &ui_form_start("@{[&get_webprefix_safe()]}/package-updates/update.cgi"); $html .= &ui_hidden("redirdesc", $text{'right_sysinfo'}); $html .= &ui_hidden("confirm", 1); $html .= &text(@vposs > 1 ? 'right_upcount' : 'right_upcount1', scalar(@vposs), &get_webprefix_safe() . '/package-updates/index.cgi?mode=updates')."
\n"; $html .= &ui_columns_start([ $text{'right_upname'}, $text{'right_updesc'}, $text{'right_upver'} ], "80%"); foreach my $p (@vposs) { $html .= &ui_columns_row([ ($p->{'name'} . &ui_hidden("u", $p->{'update'}."/".$p->{'system'})), $p->{'desc'}, $p->{'version'} ] ); } $html .= &ui_columns_end(); $html .= &ui_form_end([ [ undef, $text{'right_upok'} ] ]); push(@rv, { 'type' => 'html', 'id' => 'updates', 'open' => 1, 'desc' => $text{'right_updatesheader'}, 'html' => $html }); # Block same section from being shown by Cloudmin push(@rv, { 'type' => 'veto', 'veto' => 'updates', 'veto_module' => 'server-manager' }); } # Status of various servers if (!$data->{'nostatus'} && $info->{'startstop'} && &can_stop_servers()) { my @ss = @{$info->{'startstop'}}; my @down = grep { !$_->{'status'} } @ss; my @table; my $idir = &get_webprefix_safe() . '/'.$module_name.'/images'; foreach my $status (@ss) { # Work out label, possibly with link my $label; foreach my $l (@{$status->{'links'}}) { if ($l->{'manage'}) { $label = &ui_link(&get_webprefix_safe() . $l->{'link'}, $status->{'name'}); } } $label ||= $status->{'name'}; # Stop / start icon my $action = ($status->{'status'} ? "stop_feature.cgi" : "start_feature.cgi"); my $action_icon = ($status->{'status'} ? "" : ""); my $action_link = "{'id'}'". " title='$status->{'desc'}'>". "$action_icon"; # Restart link my $restart_link_style = $status->{'status'} ? "" : "style='visibility: hidden; pointer-events: none;' "; my $restart_link = "{'feature'}&id=$status->{'id'}'". " title='$status->{'restartdesc'}'>". "{'restartdesc'}'>\n"; push(@table, { 'desc' => " ". (!$status->{'status'} ? "" : ""). " $label", 'value' => $action_link." ".$restart_link }); } push(@rv, { 'type' => 'table', 'id' => 'status', 'desc' => $text{'right_statusheader'}, 'open' => @down ? 1 : 0, 'table' => \@table }); } # New features if ($data->{'dom'}) { $defdom = &get_domain($data->{'dom'}); if ($defdom && !&can_edit_domain($defdom)) { $defdom = undef; } } if (!$defdom && @doms) { $defdom = $doms[0]; } my $newhtml = &get_new_features_html($defdom); if ($newhtml) { push(@rv, { 'type' => 'html', 'id' => 'newfeatures', 'open' => 1, 'desc' => $text{'right_newfeaturesheader'}, 'html' => $newhtml }); } # Show usage count by type if (&master_admin() && !$data->{'novirtualmin'} && $info->{'fcount'}) { my @table; foreach my $f (@{$info->{'ftypes'}}) { my $cur = int($info->{'fcount'}->{$f}); my $extra = $info->{'fextra'}->{$f}; my $max = $info->{'fmax'}->{$f}; my $hide = $info->{'fhide'}->{$f}; if ($extra < 0 || $hide) { push(@table, { 'desc' => $text{'right_f'.$f}, 'value' => $cur }); } else { push(@table, { 'desc' => $text{'right_f'.$f}, 'value' => &text('right_out', $cur, $max) }); } } push(@rv, { 'type' => 'table', 'id' => 'ftypes', 'desc' => $text{'right_virtheader'}, 'open' => 0, 'table' => \@table }); } # Top quota users my @quota = $info->{'quota'} ? grep { &can_edit_domain($_->[0]) } @{$info->{'quota'}} : ( ); if (!$data->{'noquotas'} && @quota && (&master_admin() || &reseller_admin())) { my @usage; my $max = $data->{'max'} || 10; my $maxquota = $info->{'maxquota'}; # Work out if showing by percent makes sense my $qshow = $data->{'qshow'}; if ($qshow) { my @quotawithlimit = grep { $_->[2] } @quota; $qshow = 0 if (!@quotawithlimit); } # Limit to those with a quota limit, if showing a percent if ($qshow) { @quota = grep { $_->[2] } @quota; } if ($qshow) { # Sort by percent used @quota = grep { $_->[2] } @quota; @quota = sort { ($b->[1]+$b->[3])/$b->[2] <=> ($a->[1]+$a->[3])/$a->[2] } @quota; } else { # Sort by usage @quota = sort { $b->[1]+$b->[3] <=> $a->[1]+$a->[3] } @quota; } # Message above list my $qmsg; if (@quota > $max) { @quota = @quota[0..($max-1)]; $qmsg = &text('right_quotamax', $max); } elsif (&master_admin()) { $qmsg = $text{'right_quotaall'}; } else { $qmsg = $text{'right_quotayours'}; } my $open = 0; foreach my $q (@quota) { my $cmd = &can_edit_domain($q->[0]) ? "edit_domain.cgi" : "view_domain.cgi"; my $chart = { 'desc' => &ui_link( &get_webprefix_safe() . '/'.$module_name.'/'.$cmd.'?dom='.$q->[0]->{'id'}, &show_domain_name($q->[0])) }; if ($qshow) { # By percent used my $qpc = int($q->[1]*100 / $q->[2]); my $dpc = int($q->[3]*100 / $q->[2]); $chart->{'chart'} = [ 100, $qpc, $dpc ]; } else { # By actual usage $chart->{'chart'} = [ $maxquota, $q->[1], $q->[3] ]; } if ($q->[2]) { # Show used and limit my $pc = int(($q->[1]+$q->[3])*100 / $q->[2]); $pc = " $pc" if ($pc < 10); $chart->{'value'} = &text('right_out', &nice_size($q->[1]+$q->[3]), &nice_size($q->[2])); } else { # Just show used $chart->{'value'} = &nice_size($q->[1]+$q->[3]); } if ($q->[2] && $q->[1]+$q->[3] >= $q->[2]) { # Domain is over quota $open = 1; } push(@usage, $chart); } push(@rv, { 'type' => 'chart', 'id' => 'quota', 'desc' => $text{'right_quotasheader'}, 'open' => $open, 'header' => $qmsg, 'chart' => \@usage }); } # Top BW users my @bwdoms = grep { !$_->{'parent'} && defined($_->{'bw_usage'}) } @doms; my $maxbw = 0; foreach my $d (@doms) { $maxbw = $d->{'bw_limit'} if ($d->{'bw_limit'} > $maxbw); $maxbw = $d->{'bw_usage'} if ($d->{'bw_usage'} > $maxbw); } if (!$data->{'nobw'} && $config{'bw_active'} && @bwdoms && $maxbw) { my $qshow = $data->{'qshow'}; # Work out if showing by percent makes sense my $qshow = $data->{'qshow'}; if ($qshow) { my @domswithlimit = grep { $_->{'bw_limit'} } @doms; $qshow = 0 if (!@domswithlimit); } if ($qshow) { # Sort by percent used @doms = grep { $_->{'bw_limit'} } @doms; @doms = sort { $b->{'bw_usage'}/$b->{'bw_limit'} <=> $a->{'bw_usage'}/$a->{'bw_limit'} } @doms; } else { # Sort by usage @doms = sort { $b->{'bw_usage'} <=> $a->{'bw_usage'} } @doms; } # Show message about number of domains being displayed my $max = $data->{'max'} || 10; my $qmsg; if (@doms > $max) { @doms = @doms[0..($max-1)]; $qmsg = &text('right_quotamax', $max); } else { $qmsg = $text{'right_quotaall'}; } # Add the table of domains my $open = 0; foreach my $d (@doms) { my $cmd = &can_edit_domain($d) ? "edit_domain.cgi" : "view_domain.cgi"; my $chart = { 'desc' => &ui_link( &get_webprefix_safe() . '/'.$module_name.'/'.$cmd.'?dom='.$d->{'id'}, &show_domain_name($d)) }; my $pc = $d->{'bw_limit'} ? int($d->{'bw_usage'}*100 / $d->{'bw_limit'}) : undef; if ($qshow) { # By percent used $chart->{'chart'} = [ 100, $pc ]; } else { # By actual usage $chart->{'chart'} = [ $maxbw, $d->{'bw_usage'} ]; } # Percent used, if available if ($d->{'bw_limit'}) { $pc = " $pc" if ($pc < 10); $chart->{'value'} = &text('right_out', &nice_size($d->{'bw_usage'}), &nice_size($d->{'bw_limit'})); } else { $chart->{'value'} = &nice_size($d->{'bw_usage'}); } push(@usage, $chart); if ($d->{'bw_limit'} && $d->{'bw_usage'} >= $d->{'bw_limit'}) { $open = 1; } } push(@rv, { 'type' => 'chart', 'id' => 'bw', 'desc' => $text{'right_bwheader'}, 'open' => $open, 'header' => $qmsg, 'chart' => \@usage }); } # IP addresses used if (&master_admin() && !$data->{'noips'} && $info->{'ips'}) { my @table; my @allips = @{$info->{'ips'}}; push(@allips, @{$info->{'ips6'}}) if ($info->{'ips6'}); foreach my $ipi (@allips) { my $umsg; if ($ipi->[3] == 1) { $umsg = "$ipi->[4]"; } else { my $slink = &get_webprefix_safe() . '/'.$module_name. '/search.cgi?field=ip&what='.$ipi->[0]; $umsg = &ui_link($slink, &text('right_ips', $ipi->[3])); } push(@table, { 'desc' => $ipi->[0], 'value' => ($ipi->[1] eq 'def' ? $text{'right_defip'} : $ipi->[1] eq 'reseller' ? text('right_reselip', $ipi->[2]) : $ipi->[1] eq 'shared' ? $text{'right_sharedip'} : $text{'right_ip'})." ".$umsg }); } if ($info->{'ipranges'}) { foreach my $r (@{$info->{'ipranges'}}) { push(@table, { 'desc' => $r->[0], 'value' => &text('right_iprange', $r->[1], $r->[2]), 'wide' => 1 }); } } push(@rv, { 'type' => 'table', 'id' => 'ips', 'desc' => $text{'right_ipsheader'}, 'open' => 0, 'table' => \@table }); } # Programs and versions if (!$data->{'nosysinfo'} && $info->{'progs'} && &can_view_sysinfo()) { my @table; foreach my $info (@{$info->{'progs'}}) { push(@table, { 'desc' => $info->[0], 'value' => $info->[1] }); } push(@rv, { 'type' => 'table', 'id' => 'sysinfo', 'desc' => $text{'right_sysinfoheader'}, 'open' => 0, 'table' => \@table }); } # Virtualmin licence my %vserial; if (&read_env_file($virtualmin_license_file, \%vserial) && $vserial{'SerialNumber'} ne 'GPL' && &master_admin()) { my @table; my $open = 0; # Serial and key push(@table, { 'desc' => $text{'right_vserial'}, 'value' => $vserial{'SerialNumber'} }); # Allowed domain counts my ($dleft, $dreason, $dmax, $dhide) = &count_domains("realdoms"); push(@table, { 'desc' => $text{'right_vmax'}, 'value' => $dmax <= 0 ? $text{'right_vunlimited'} : $dmax }); push(@table, { 'desc' => $text{'right_vleft'}, 'value' => $dleft < 0 ? $text{'right_vunlimited'} : $dleft }); # Add allowed domain counts my %lstatus; &read_file($licence_status, \%lstatus); if ($lstatus{'used_servers'}) { push(@table, { 'desc' => $text{'right_smax'}, 'value' => $lstatus{'servers'} || $text{'right_vunlimited'} }); push(@table, { 'desc' => $text{'right_sused'}, 'value' => $lstatus{'used_servers'} }); } # Show license expiry date if ($lstatus{'expiry'} =~ /^203[2-8]-/) { push(@table, { 'desc' => $text{'right_expiry'}, 'value' => $text{'right_expirynever'} }); } elsif ($lstatus{'expiry'}) { push(@table, { 'desc' => $text{'right_expiry'}, 'value' => $lstatus{'expiry'} }); my $ltm = &parse_license_date($lstatus{'expiry'}); if ($ltm) { my $days = int(($ltm - time()) / (24*60*60)); push(@table, { 'desc' => $text{'right_expirydays'}, 'value' => $days < 0 ? &text('right_expiryago', -$days) : $days }); $open = 1 if ($days < 7); } } push(@rv, { 'type' => 'table', 'id' => 'serial', 'desc' => $text{'right_licenceheader'}, 'open' => $open, 'table' => \@table }); # Re-check licence link push(@rv, { 'type' => 'link', 'priority' => 20, 'desc' => $text{'right_vlcheck'}, 'link' => '/'.$module_name.'/licence.cgi' }); } # Documentation links my $doclink = &get_virtualmin_docs(); push(@rv, { 'type' => 'link', 'priority' => 50, 'desc' => $text{'right_virtdocs'}, 'target' => 'new', 'link' => $doclink }); if ($config{'docs_link'}) { push(@rv, { 'type' => 'link', 'priority' => 49, 'desc' => $text{'right_virtdocs2'}, 'target' => 'new', 'link' => $config{'docs_link'} }); } # Sections defined by plugins my $level = &master_admin() ? 0 : &reseller_admin() ? 2 : 1; foreach my $p (&list_plugin_sections($level)) { push(@rv, { 'type' => 'html', 'id' => 'plugin_'.$p->{'name'}, 'desc' => $p->{'title'}, 'html' => $p->{'html'}, 'open' => $p->{'status'} }); } return @rv; } sub get_virtualmin_docs { return "https://www.virtualmin.com/docs/"; } sub parse_license_date { my ($str) = @_; if ($str =~ /^(\d{4})-(\d+)-(\d+)$/) { return eval { timelocal(0, 0, 0, $3, $2-1, $1) }; } return undef; } 1;