# Virtualmin Framed Theme # Icons copyright David Vignoni, all other theme elements copyright 2005-2007 # Virtualmin, Inc. $main::cloudmin_no_create_links = 1; $main::cloudmin_no_edit_buttons = 1; $main::cloudmin_no_global_links = 1; $main::mailbox_no_addressbook_button = 1; $main::mailbox_no_folder_button = 1; $main::basic_virtualmin_menu = 1; $main::basic_virtualmin_domain = 1; $main::nocreate_virtualmin_menu = 1; $main::nosingledomain_virtualmin_mode = 1; our $ui_formcount; # Global state for wrapper # if 0, wrapper isn't on, add one and open it, if 1 close it, if 2+, subtract # but don't close $main::WRAPPER_OPEN = 0; $main::COLUMNS_WRAPPER_OPEN = 0; # theme_ui_print_header(subtext, header-args...) # Prints the page header like the core, but wraps the text under the # title in a span of its own, then prints the subtext block sub theme_ui_print_header { my ($text, @args) = @_; if ($args[9] ne '') { # Text under the title, such as a version, gets an element of its own # so the stylesheet can make it smaller $args[9] = "$args[9]"; } &header(@args); print &ui_post_header($text); } # theme_ui_post_header([subtext]) # Returns HTML to appear directly after a standard header() call sub theme_ui_post_header { my ($text) = @_; my $rv; $rv .= "
$text
\n" if (defined($text)); $rv .= "

" if (!defined($text)); return $rv; } # theme_ui_pre_footer() # Returns HTML to appear directly before a standard footer() call sub theme_ui_pre_footer { my $rv; $rv .= "

\n"; return $rv; } # ui_print_footer(args...) # Print HTML for a footer with the pre-footer line. Args are the same as those # passed to footer() sub theme_ui_print_footer { local @args = @_; print &ui_pre_footer(); if ($ui_formcount) { print < (function(){ var forms = document.forms || []; for(var i = 0; i < forms.length; i++){ for(var j = 0; j < forms[i].length; j++){ if(!forms[i][j].readonly != undefined && forms[i][j].type != "hidden" && forms[i][j].disabled != true && forms[i][j].style.display != 'none'){ forms[i][j].focus(); return; } } } })(); EOL } &footer(@args); } sub theme_icons_table { my ($i, $need_tr); my $cols = $_[3] ? $_[3] : 4; my $per = int(100.0 / $cols); print "
\n"; print "\n"; for($i=0; $i<@{$_[0]}; $i++) { if ($i%$cols == 0) { print "\n"; } print "\n"; if ($i%$cols == $cols-1) { print "\n"; } } while($i++%$cols) { print "\n"; $need_tr++; } print "\n" if ($need_tr); print "
\n"; &generate_icon($_[2]->[$i], $_[1]->[$i], $_[0]->[$i], $_[4], $_[5], $_[6], $_[7]->[$i], $_[8]->[$i]); print "
\n"; print "
\n"; } sub theme_generate_icon { my $w = !defined($_[4]) ? "width=48" : $_[4] ? "width=$_[4]" : ""; my $h = !defined($_[5]) ? "height=48" : $_[5] ? "height=$_[5]" : ""; if ($tconfig{'noicons'}) { if ($_[2]) { print "$_[6]$_[1]$_[7]\n"; } else { print "$_[6]$_[1]$_[7]\n"; } } elsif ($_[2]) { print "
\n", "\"\"
\n"; print "$_[6]$_[1]$_[7]\n"; } else { print "
\n", "\"\"", "
\n$_[6]$_[1]$_[7]\n"; } } # theme_post_save_domain(&domain, action) # Called by Virtualmin after a domain is updated, to refresh the left menu sub theme_post_save_domain { local ($d, $action) = @_; # Refresh left side, in case options have changed print "\n"; } # theme_post_save_domains([domain, action]+) # Called after multiple domains are updated, to refresh the left menu sub theme_post_save_domains { print "\n"; } # theme_post_save_server(&server, action) # Called by Cloudmin after a server is updated, to refresh the left menu sub theme_post_save_server { local ($s, $action) = @_; if ($action eq 'create' || $action eq 'delete' || !$done_theme_post_save_server++) { print "\n"; } } # theme_select_server(&server) # Called by Cloudmin when a page for a server is displayed, to select it on the # left menu. sub theme_select_server { local ($server) = @_; print < if (window.parent && window.parent.frames[0]) { var leftdoc = window.parent.frames[0].document; var leftform = leftdoc.forms[0]; if (leftform) { var serversel = leftform['sid']; if (serversel && serversel.value != '$server->{'id'}' || !serversel) { //if (serversel) { // // Need to change value of selector // serversel.value = '$server->{'id'}'; // } window.parent.frames[0].location = '@{[&get_webprefix()]}/left.cgi?mode=server-manager&sid=$server->{'id'}'; } } } EOF } # theme_select_domain(&domain) # Called by Virtualmin when a page for a server is displayed, to select it on # the left menu. sub theme_select_domain { local ($d) = @_; print < if (window.parent && window.parent.frames[0]) { var leftdoc = window.parent.frames[0].document; var leftform = leftdoc.forms[0]; if (leftform) { var domsel = leftform['dom']; if (domsel && domsel.value != '$d->{'id'}') { // Need to change value // domsel.value = '$d->{'id'}'; window.parent.frames[0].location = '@{[&get_webprefix()]}/left.cgi?mode=virtual-server&dom=$d->{'id'}'; } } } EOF } # theme_post_save_folder(&folder, action) # Called after some folder is changed, to refresh the left frame. The action # may be 'create', 'delete', 'modify' or 'read' sub theme_post_save_folder { local ($folder, $action) = @_; my $ref; if ($action eq 'create' || $action eq 'delete' || $action eq 'modify') { # Always refresh $ref = 1; } else { # Only refresh if showing unread count if (defined(&mailbox::should_show_unread) && &mailbox::should_show_unread($folder)) { $ref = 1; } } if ($ref) { print "\n"; } } sub theme_post_change_modules { print < var url = '' + top.left.location; if (url.indexOf('mode=modules') > 0) { top.left.location = url; } EOF } # theme_prebody(header-args...) # Hides the module index link on Virtualmin pages, whose menu is already # in the left frame sub theme_prebody { if (get_module_name() eq "virtual-server") { # No need for Module Index link, as we have the left-side frame $tconfig{'nomoduleindex'} = 1; } } # theme_prehead() # Prints the head parts of every page: the color scheme meta, the scheme # and frame scripts, and the links to the stylesheet and the table sorting # script. Also sets the body attributes the stylesheet keys on: the module # name as a class and the forced color scheme, if any. sub theme_prehead { my $pfx = &get_webprefix(); my $scheme = &theme_color_scheme(); print "\n"; print &theme_scheme_script($scheme); print &theme_frame_script(); print "\n"; print "\n"; my @cls = grep { $_ } ( $main::gray_theme_body_class, &get_module_name() ); $tconfig{'inbody'} = (@cls ? "class='".join(" ", @cls)."'" : ""). ($scheme ? " data-scheme='$scheme'" : ""); } # theme_scheme_script([forced-scheme]) # Returns the scheme bootstrap and frame-wide keyboard shortcuts. The optional # forced scheme is the configured default. Alt+T stores a per-tab override # without changing that setting, and Alt+R reloads only the content frame. # Scheme changes reach the frameset and both frames. An automatic scheme uses # its last value before first paint, follows system changes, and is checked # again after load. sub theme_scheme_script { my ($forced) = @_; $forced = $forced && $forced =~ /^(light|dark)$/ ? $1 : ""; my $rv = <<'EOF'; EOF $rv =~ s/__GRAY_THEME_FORCED_SCHEME__/$forced/g; return $rv; } # theme_frame_script() # Returns a script for a page shown in the right frame, which tells the # menu frame when the page starts to leave and when the next one has # loaded, so the menu can show a loader in between. It does nothing in # the menu frame itself, in popups and outside the frameset. sub theme_frame_script { return <<'EOF'; EOF } # theme_color_scheme() # Returns light or dark when the theme settings force a color scheme, or an # empty string to follow the browser. The setting is stored with the other # system information page settings, per user unless they are global. sub theme_color_scheme { return $main::gray_theme_scheme if (defined($main::gray_theme_scheme)); my $file = "$config_directory/$current_theme/sections"; my %sects; &read_file($file, \%sects); if (!$sects{'global'}) { # Unless the global settings are forced on everyone, the user's own # file replaces them my %usects; %sects = %usects if (&read_file("$file.$remote_user", \%usects)); } $main::gray_theme_scheme = $sects{'scheme'} =~ /^(light|dark)$/ ? $1 : ""; return $main::gray_theme_scheme; } # theme_asset_key(file) # Returns the modification time of a file under unauthenticated, used as a # cache key in its URL so browsers pick up a changed file at once sub theme_asset_key { my ($file) = @_; my @st = stat("$theme_root_directory/unauthenticated/$file"); return @st ? $st[9] : &get_webmin_version(); } # theme_tag_class(tags, class) # Adds a class to a string of tag attributes, merging it into an existing # class attribute so that a tag never gets two of them sub theme_tag_class { my ($tags, $class) = @_; return "class='$class'" if (!$tags); return $tags if ($tags =~ s/class=(['"])([^'"]*)\1/class=$1$2 $class$1/); return "$tags class='$class'"; } # theme_popup_prehead(title, ...) # Popup windows and the frames get the same head as ordinary pages sub theme_popup_prehead { return &theme_prehead(); } # theme_ui_table_start(heading, [tabletags], [cols], [&default-tds], # [right-heading]) # Returns HTML for the start of a form table: a card with the heading in # its head row and the label and value rows in a nested table sub theme_ui_table_start { my ($heading, $tabletags, $cols, $tds, $rightheading) = @_; # Expose the column count so CSS can size the form layout. my $formcols = $cols || 4; if (! $tabletags =~ /width/) { $tabletages .= " width=100%"; } if (defined($main::ui_table_cols)) { # Push on stack, for nested call push(@main::ui_table_cols_stack, $main::ui_table_cols); push(@main::ui_table_pos_stack, $main::ui_table_pos); push(@main::ui_table_default_tds_stack, $main::ui_table_default_tds); } my $rv; my $colspan = 1; if (!$main::WRAPPER_OPEN) { # A class in the table tags, such as the login form's, joins the # wrapper class instead of being lost as a second class attribute $rv .= "\n"; $rv .= "\n"; $rv .= "
\n"; } $main::WRAPPER_OPEN++; $rv .= "\n"; if (defined($heading) || defined($rightheading)) { # Heading row, with an optional part at the right $rv .= ""; # Add the main heading when present. if (defined($heading)) { $rv .= "" } # Put optional actions or context at the right. if (defined($rightheading)) { $rv .= ""; $colspan++; } $rv .= "\n"; } $rv .= "
$heading$rightheading
". "\n"; $main::ui_table_cols = $formcols; $main::ui_table_pos = 0; $main::ui_table_default_tds = $tds; return $rv; } # ui_table_row(label, value, [cols], [&td-tags]) # Returns HTML for a row in a table started by ui_table_start, with a 1-column # label and 1+ column value. sub theme_ui_table_row { my ($label, $value, $cols, $tds, $trs) = @_; $cols ||= 1; $tds ||= $main::ui_table_default_tds; my $rv; if ($main::ui_table_pos+$cols+1 > $main::ui_table_cols && $main::ui_table_pos != 0) { # If the requested number of cols won't fit in the number # remaining, start a new row my $leftover = $main::ui_table_cols - $main::ui_table_pos; $rv .= "\n"; $rv .= "\n"; $main::ui_table_pos = 0; } if (defined($label) && ($value =~ /id="([^"]+)"/ || $value =~ /id='([^']+)'/ || $value =~ /id=([^>\s]+)/)) { # Value contains an input with an ID my $id = $1; $label = ""; } my $trtags_attrs = ref($trs) eq 'ARRAY' && $trs->[0] ? " $trs->[0]" : ""; my $trtags_class = ref($trs) eq 'ARRAY' && $trs->[1] ? " $trs->[1]" : ""; $rv .= "\n" if ($main::ui_table_pos%$main::ui_table_cols == 0); $rv .= "\n" if (defined($label)); $rv .= "\n"; $main::ui_table_pos += $cols+(defined($label) ? 1 : 0); if ($main::ui_table_pos%$main::ui_table_cols == 0) { $rv .= "\n"; $main::ui_table_pos = 0; } return $rv; } sub theme_ui_table_hr { my $rv; if ($main::ui_table_pos) { $rv .= "\n"; $main::ui_table_pos = 0; } $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; return $rv; } # theme_ui_table_end() # Returns HTML for the end of a table started by theme_ui_table_start sub theme_ui_table_end { my $rv; if ($main::ui_table_cols == 4 && $main::ui_table_pos) { # Add an empty block to balance the table $rv .= &ui_table_row(" ", " "); } if (@main::ui_table_cols_stack) { # Back to the column state of the enclosing table $main::ui_table_cols = pop(@main::ui_table_cols_stack); $main::ui_table_pos = pop(@main::ui_table_pos_stack); $main::ui_table_default_tds = pop(@main::ui_table_default_tds_stack); } else { # No enclosing table $main::ui_table_cols = undef; $main::ui_table_pos = undef; $main::ui_table_default_tds = undef; } $rv .= "
[0]>$label[1]>$value

\n"; if ($main::WRAPPER_OPEN==1) { # Close the card around the outermost table $rv .= "
\n"; } $main::WRAPPER_OPEN--; return $rv; } # theme_ui_tabs_start(&tabs, name, selected, show-border) # Render a row of tabs from which one can be selected. Each tab is an array # ref containing a name, title and link. The tabs are links in a div, and # select_tab switches their classes and the visible tab body. sub theme_ui_tabs_start { my ($tabs, $name, $sel, $border) = @_; my $rv; if (!$main::ui_hidden_start_donejs++) { # The tab switching script is printed once per page $rv .= &ui_hidden_javascript(); } # List of tab names, used by select_tab to find the tabs and bodies my $tabnames = &convert_to_json([map { $_->[0] } @$tabs]); $rv .= "\n"; # Output the tabs $rv .= &ui_hidden($name, $sel)."\n"; $rv .= "
\n"; foreach my $t (@$tabs) { my $cls = $t->[0] eq $sel ? "ui_tab ui_tab_selected" : "ui_tab"; my $href = $t->[2] ne '' ? $t->[2] : '#'; $rv .= "". "$t->[1]\n"; } $rv .= "
\n"; if ($border) { # All tab bodies are within a box $rv .= "
\n"; } $main::ui_tabs_selected = $sel; return $rv; } # theme_ui_tabs_end(show-border) # Closes the box opened by theme_ui_tabs_start sub theme_ui_tabs_end { my ($border) = @_; return $border ? "
\n" : ""; } # theme_ui_columns_start(&headings, [width-percent], [no-border], # [&td-tags], [title], [sortable], [class]) # Returns HTML for a multi-column table. The sortable flag forces client-side # sorting, and class adds a table class. sub theme_ui_columns_start { my ($heads, $width, $noborder, $tdtags, $title, $sortable, $class) = @_; my ($href) = grep { $_ =~ /\n"; $rv .= "
\n"; } if (!$noborder) { # Nested bordered tables share that card $main::COLUMNS_WRAPPER_OPEN++; } # Tables are sorted by sorttable.js unless their headings are links, or # always when the caller asked for a sortable table my @classes; push(@classes, "ui_table") if (!$noborder); push(@classes, "sortable") if (!$href || $sortable); push(@classes, "ui_columns"); push(@classes, $class) if ($class); $rv .= "\n"; if ($title) { # Title row spanning the table, above the column headings $rv .= " ". "$title". " \n"; } $rv .= " \n"; my $i; for($i=0; $i<@$heads; $i++) { $rv .= "[$i].">". ($heads->[$i] eq "" ? "
" : $heads->[$i])."
\n"; } $rv .= " \n"; $theme_ui_columns_count++; return $rv; } # theme_ui_columns_header(&columns, &tdtags) # Returns HTML for a heading row inside a multi-column table sub theme_ui_columns_header { my ($cols, $tdtags) = @_; my $rv; $rv .= "\n"; for(my $i=0; $i<@$cols; $i++) { $rv .= "[$i].">". ($cols->[$i] eq "" ? "
" : $cols->[$i])."
\n"; } $rv .= "\n"; return $rv; } # theme_ui_columns_row(&columns, &tdtags) # Returns HTML for a row in a multi-column table. Rows are highlighted on # hover and when checked by the stylesheet, so no handlers are needed. sub theme_ui_columns_row { $theme_ui_columns_row_toggle = $theme_ui_columns_row_toggle ? '0' : '1'; local ($cols, $tdtags) = @_; my $rv; $rv .= "\n"; my $i; for($i=0; $i<@$cols; $i++) { $rv .= "[$i].">". ($cols->[$i] !~ /\S/ ? "
" : $cols->[$i])."\n"; } $rv .= "\n"; return $rv; } # theme_ui_columns_end() # Ends a table from theme_ui_columns_start. Also closes the scroll container # and card opened for the outermost bordered table. sub theme_ui_columns_end { my $rv; $rv = " \n"; # Close the shared scroll container with the outermost table. if ($main::COLUMNS_WRAPPER_OPEN == 1) { $rv .= "
\n"; } $main::COLUMNS_WRAPPER_OPEN--; return $rv; } # theme_ui_grid_table(&elements, columns, [width-percent], [tds], [tabletags], # [title]) # Given a list of HTML elements, formats them into a table with the given # number of columns. However, themes are free to override this to use fewer # columns where space is limited. sub theme_ui_grid_table { my ($elements, $cols, $width, $tds, $tabletags, $title) = @_; return "" if (!@$elements); my $rv = "\n"; $rv .= "
\n"; $rv .= "\n"; if ($title) { $rv .= " ". "\n"; } $rv .= "\n"; my $i; for($i=0; $i<@$elements; $i++) { $rv .= "" if ($i%$cols == 0); $rv .= "\n"; $rv .= "" if ($i%$cols == $cols-1); } if ($i%$cols) { while($i%$cols) { $rv .= "\n"; $i++; } $rv .= "\n"; } $rv .= "
$title
[$i%$cols]." valign=top class='ui_grid_cell'>". $elements->[$i]."
[$i%$cols]." class='ui_grid_cell'>
\n"; $rv .= "
\n"; # wrapper return $rv; } sub theme_ui_hidden_start { my ($title, $name, $status) = @_; my $rv; my $opened = $status ? " open" : ""; $rv .= "
"; $rv .= "$title\n"; return $rv; } =head2 ui_hidden_end(name) Returns HTML for the end of a hidden section, started by ui_hidden_start. =cut sub theme_ui_hidden_end { return "
\n"; } # theme_ui_hidden_table_start(heading, [tabletags], [cols], name, status, # [&default-tds], [rightheading]) # A table with a heading and table inside, and which is collapsible sub theme_ui_hidden_table_start { my ($heading, $tabletags, $cols, $name, $status, $tds, $rightheading) = @_; my $rv; if (!$main::ui_hidden_start_donejs++) { $rv .= &ui_hidden_javascript(); } my $opened = $status ? " open" : ""; my $header = defined($heading) ? "$heading" : ""; my $rheader = defined($rightheading) ? "$rightheading" : ""; if (!$main::WRAPPER_OPEN) { # If we're not already inside of a wrapper, wrap it $rv .= "
\n"; } $main::WRAPPER_OPEN++; my $colspan = 1; $rv .= "
"; $rv .= "$header $rheader\n"; $rv .= "\n"; $main::ui_table_cols = $cols || 4; $main::ui_table_pos = 0; $main::ui_table_default_tds = $tds; return $rv; } # ui_hidden_table_end(name) # Returns HTML for the end of table with hiding, as started by # ui_hidden_table_start sub theme_ui_hidden_table_end { local $rv = "
\n"; if ( $main::WRAPPER_OPEN == 1 ) { $main::WRAPPER_OPEN--; $rv .= "
\n"; } elsif ($main::WRAPPER_OPEN) { $main::WRAPPER_OPEN--; } return $rv; } # theme_ui_checked_columns_row(&columns, &tdtags, checkname, checkvalue, # [checked], [disabled], [tags]) # Returns HTML for a row with a checkbox in its first column. The row is # highlighted by the stylesheet while its checkbox is checked. sub theme_ui_checked_columns_row { $theme_ui_columns_row_toggle = $theme_ui_columns_row_toggle ? '0' : '1'; local ($cols, $tdtags, $checkname, $checkvalue, $checked, $disabled, $tags) = @_; my $rv; my $rid = "e_escape("row_${checkname}_${checkvalue}"); my $mycb = &theme_tag_class($cb, "row$theme_ui_columns_row_toggle ui_checked_columns"); $rv .= "\n"; $rv .= "[0].">". &ui_checkbox($checkname, $checkvalue, undef, $checked, $tags, $disabled). "\n"; my $i; for($i=0; $i<@$cols; $i++) { $rv .= "[$i+1].">"; if ($cols->[$i] !~ /"; } $rv .= ($cols->[$i] !~ /\S/ ? "
" : $cols->[$i]); if ($cols->[$i] !~ /\n"; $rv .= "[0]." class='ui_radio_radio'>". &ui_oneradio($checkname, $checkvalue, undef, $checked). "\n"; my $i; for($i=0; $i<@$cols; $i++) { $rv .= "[$i+1].">"; if ($cols->[$i] !~ /"; } $rv .= ($cols->[$i] !~ /\S/ ? "
" : $cols->[$i]); if ($cols->[$i] !~ / 14, 'title' => $direction eq "left" ? '<-' : '->' }); if ($disabled) { # A disabled arrow is plain, not a link return "$icon\n"; } else { # Arrow linking to the page return "$icon\n"; } } # theme_ui_links_row(&links) # Returns links in a toolbar wrapper. Gray Theme hides the legacy separators # and styles each link as a button. sub theme_ui_links_row { my ($links) = @_; # An empty list does not need a toolbar container. return "" if (!$links || !@$links); my @button_links = map { my $link = $_; # Drop the final period from the button without changing its source text. $link =~ s/(?]+>)*\s*<\/a>\s*$)//i; $link; } @$links; return "\n"; } # theme_file_chooser_button(input, type, [form], [chroot], [addmode]) # Returns a button that opens the file chooser, in a window large enough # for the theme's controls unless the Webmin configuration sets a size sub theme_file_chooser_button { my ($input, $type, $form, $chroot, $add) = @_; $chroot = "/" if (!defined($chroot)); $add = int($add); my ($w, $h) = (560, 500); ($w, $h) = split(/x/, $gconfig{'db_sizefile'}) if ($gconfig{'db_sizefile'}); return "\n"; } # theme_user_chooser_button(input, multiple, [form]) # Returns a button that opens the user chooser, sized like the file chooser sub theme_user_chooser_button { my ($input, $multi) = @_; return &theme_chooser_button("user_chooser.cgi", "user", $input, $multi); } # theme_group_chooser_button(input, multiple, [form]) # Returns a button that opens the group chooser, sized like the file chooser sub theme_group_chooser_button { my ($input, $multi) = @_; return &theme_chooser_button("group_chooser.cgi", "group", $input, $multi); } # theme_chooser_button(script, param, input, multiple) # Returns the button shared by the user and group choosers. The multiple # selection window has two panes and so is wider. sub theme_chooser_button { my ($script, $param, $input, $multi) = @_; my ($w, $h) = $multi ? (640, 500) : (420, 500); if ($multi && $gconfig{'db_sizeusers'}) { # Size of the multiple selection chooser from the Webmin config ($w, $h) = split(/x/, $gconfig{'db_sizeusers'}); } elsif (!$multi && $gconfig{'db_sizeuser'}) { # Size of the single selection chooser from the Webmin config ($w, $h) = split(/x/, $gconfig{'db_sizeuser'}); } $multi = int($multi); return "\n"; } # theme_footer([page, name]+, [noendbody]) # Output a footer for returning to some page sub theme_footer { my $i; my @links; my %module_info = get_module_info(get_module_name()); for($i=0; $i+1<@_; $i+=2) { local $url = $_[$i]; if ($url ne '/' || !$tconfig{'noindex'}) { # Turn the shortcut into a URL, unless the theme hides the # Webmin index link if ($url eq '/') { # Webmin index, opened at the module's category $url = "/?cat=$module_info{'category'}"; } elsif ($url eq '' && get_module_name() eq 'virtual-server' || $url eq '/virtual-server/') { # Don't bother with virtualmin menu next; } elsif ($url eq '' && get_module_name() eq 'server-manager' || $url eq '/server-manager/') { # Don't bother with Cloudmin menu next; } #elsif ($url =~ /(view|edit)_domain.cgi/ && # get_module_name() eq 'virtual-server' || # $url =~ /^\/virtual-server\/(view|edit)_domain.cgi/) { # # Don't bother with link to domain details # next; # } elsif ($url =~ /edit_serv.cgi/ && get_module_name() eq 'server-manager' || $url =~ /^\/virtual-server\/edit_serv.cgi/) { # Don't bother with link to system details next; } elsif ($url eq '' && get_module_name()) { # Index page of the current module $url = "/".get_module_name()."/". $module_info{'index_link'}; } elsif ($url =~ /^\?/ && get_module_name()) { # Index page of the module with parameters $url = "/".get_module_name()."/$url"; } $url = "@{[&get_webprefix()]}$url" if ($url =~ /^\//); push(@links, "". &text('main_return', $_[$i+1]).""); } } if (@links) { # Return links, with a chevron before the first print "\n"; } if (!$_[$i]) { # End the page, unless the caller keeps the body open my $postbody = $tconfig{'postbody'}; if ($postbody) { # Text from the theme config at the end of every page, with # its placeholders filled in my $hostname = &get_display_hostname(); my $version = &get_webmin_version(); my $os_type = $gconfig{'real_os_type'} || $gconfig{'os_type'}; my $os_version = $gconfig{'real_os_version'} || $gconfig{'os_version'}; $postbody =~ s/%HOSTNAME%/$hostname/g; $postbody =~ s/%VERSION%/$version/g; $postbody =~ s/%USER%/$remote_user/g; $postbody =~ s/%OS%/$os_type $os_version/g; print "$postbody\n"; } if ($tconfig{'postbodyinclude'}) { # File from the theme directory at the end of every page local $_; open(INC, "$theme_root_directory/$tconfig{'postbodyinclude'}"); while() { print; } close(INC); } if (defined(&theme_postbody)) { # Hook of an overlay theme &theme_postbody(@_); } print "\n"; } } # theme_redirect(original-url, url) # Prints a redirect like the core, except that a Virtualmin page sending # the browser to the server root goes to the framed system information # page, since the Virtualmin menu is already in the left frame sub theme_redirect { local ($orig, $url) = @_; if (get_module_name() eq "virtual-server" && $orig eq "" && # Virtualmin's return to the root $url =~ /^((http|https):\/\/([^\/]+))\//) { $url = "$1/right.cgi"; } print "Location: $url\n\n"; } # theme_ui_hidden_javascript() # Returns the script that switches tabs and opens hidden sections. Both # only change classes; the stylesheet does the rest. sub theme_ui_hidden_javascript { return < // Open or close a hidden section function hidden_opener(divid, openerid) { var divobj = document.getElementById(divid); var openerobj = document.getElementById(openerid); var shown = divobj.className == 'opener_shown'; divobj.className = shown ? 'opener_hidden' : 'opener_shown'; if (openerobj) { // Turn the arrow of the link that opens the section openerobj.className = shown ? 'opener_closed' : 'opener_open'; } } // Show a tab and hide the others function select_tab(name, tabname, form) { var tabnames = document[name+'_tabnames']; for(var i=0; i