One Hat Cyber Team
  • Dir : ~/usr/share/webmin/
  • View File Name : ui-lib.pl
    "; } =head2 ui_user_textbox(name, value, [form], [disabled?], [tags]) Returns HTML for an input for selecting a Unix user. Parameters are the same as ui_textbox. =cut sub ui_user_textbox { my ($name, $value, $form, $dis, $tags) = @_; return &theme_ui_user_textbox(@_) if (defined(&theme_ui_user_textbox)); return &ui_textbox($name, $value, 13, $dis, undef, $tags)." ". &user_chooser_button($name, 0, $form); } =head2 ui_users_textbox(name, value, [form], [disabled?], [tags]) Returns HTML for an input for selecting multiple Unix users. Parameters are the same as ui_textbox. =cut sub ui_users_textbox { my ($name, $value, $form, $dis, $tags) = @_; return &theme_ui_users_textbox(@_) if (defined(&theme_ui_users_textbox)); return &ui_textbox($name, $value, 60, $dis, undef, $tags)." ". &user_chooser_button($name, 1, $form); } =head2 ui_group_textbox(name, value, [form], [disabled?], [tags]) Returns HTML for an input for selecting a Unix group. Parameters are the same as ui_textbox. =cut sub ui_group_textbox { my ($name, $value, $form, $dis, $tags) = @_; return &theme_ui_group_textbox(@_) if (defined(&theme_ui_group_textbox)); return &ui_textbox($name, $value, 13, $dis, undef, $tags)." ". &group_chooser_button($name, 0, $form); } =head2 ui_groups_textbox(name, value, [form], [disabled?], [tags]) Returns HTML for an input for selecting Unix groups. Parameters are the same as ui_textbox. =cut sub ui_groups_textbox { my ($name, $value, $form, $dis, $tags) = @_; return &theme_ui_groups_textbox(@_) if (defined(&theme_ui_groups_textbox)); return &ui_textbox($name, $value, 60, $dis, undef, $tags)." ". &group_chooser_button($name, 1, $form); } =head2 ui_opt_textbox(name, value, size, option1, [option2], [disabled?], [&extra-fields], [max]) Returns HTML for a text field that is optional, implemented by default as a field with radio buttons next to it. The parameters are : =item name - HTML name for the text box. The radio buttons will have the same name, but with _def appended. =item value - Initial value, or undef if you want the default radio button selected initially. =item size - Width of the text box in characters. =item option1 - Text for the radio button for selecting that no input is being given, such as 'Default'. =item option2 - Text for the radio button for selecting that you will provide input. =item disabled - Set to 1 to disable this input by default. =item extra-fields - An optional array ref of field names that should be disabled by Javascript when this field is disabled. =item max - Optional maximum allowed input length, in characters. =item tags - Additional HTML attributes for the text box =item type - HTML input type, which defaults to "text" =cut sub ui_opt_textbox { return &theme_ui_opt_textbox(@_) if (defined(&theme_ui_opt_textbox)); my ($name, $value, $size, $opt1, $opt2, $dis, $extra, $max, $tags, $type) = @_; $type ||= "text"; my $dis1 = &js_disable_inputs([ $name, @$extra ], [ ]); my $dis2 = &js_disable_inputs([ ], [ $name, @$extra ]); my $rv; $size = &ui_max_text_width($size); $rv .= &ui_radio($name."_def", $value eq '' ? 1 : 0, [ [ 1, $opt1, "onClick='$dis1'" ], [ 0, $opt2 || " ", "onClick='$dis2'" ] ], $dis)."\n"; $rv .= ""; return $rv; } =head2 ui_submit(label, [name], [disabled?], [tags]) Returns HTML for a form submit button. Parameters are : =item label - Text to appear on the button. =item name - Optional HTML name for the button. Useful if the CGI it submits to needs to know which of several buttons was clicked. =item disabled - Set to 1 if this button should be disabled by default. =item tags - Additional HTML attributes for the tag. =cut sub ui_submit { return &theme_ui_submit(@_) if (defined(&theme_ui_submit)); my ($label, $name, $dis, $tags) = @_; return "\n"; } =head2 ui_reset(label, [disabled?], [tags]) Returns HTML for a form reset button, which clears all fields when clicked. Parameters are : =item label - Text to appear on the button. =item disabled - Set to 1 if this button should be disabled by default. =item tags - Additional HTML attributes for the tag. =cut sub ui_reset { return &theme_ui_reset(@_) if (defined(&theme_ui_reset)); my ($label, $dis, $tags) = @_; return "\n"; } =head2 ui_button(label, [name], [disabled?], [tags]) Returns HTML for a form button, which doesn't do anything when clicked unless you add some Javascript to it. The parameters are : =item label - Text to appear on the button. =item name - HTML name for this input. =item disabled - Set to 1 if this button should be disabled by default. =item tags - Additional HTML attributes for the tag, typically Javascript inside an onClick attribute. =cut sub ui_button { return &theme_ui_button(@_) if (defined(&theme_ui_button)); my ($label, $name, $dis, $tags) = @_; return "\n"; } =head2 ui_date_input(day, month, year, day-name, month-name, year-name, [disabled?]) Returns HTML for a date-selection field, with day, month and year inputs. The parameters are : =item day - Initial day of the month. =item month - Initial month of the year, indexed from 1. =item year - Initial year, four-digit. =item day-name - Name of the day input field. =item month-name - Name of the month select field. =item year-name - Name of the year input field. =item disabled - Set to 1 to disable all fields by default. =cut sub ui_date_input { return &theme_ui_date_input(@_) if (defined(&theme_ui_date_input)); my ($day, $month, $year, $dayname, $monthname, $yearname, $dis) = @_; my $rv; $rv .= ""; $rv .= &ui_textbox($dayname, $day, 3, $dis); $rv .= "/"; $rv .= &ui_select($monthname, $month, [ map { [ $_, $text{"smonth_$_"} ] } (1 .. 12) ], 1, 0, 0, $dis); $rv .= "/"; $rv .= &ui_textbox($yearname, $year, 5, $dis); $rv .= ""; return $rv; } =head2 ui_buttons_start Returns HTML for the start of a block of action buttons with descriptions, as generated by ui_buttons_row. Some example code : print ui_buttons_start(); print ui_buttons_row('start.cgi', 'Start server', 'Click this button to start the server process'); print ui_buttons_row('stop.cgi', 'Stop server', 'Click this button to stop the server process'); print ui_buttons_end(); =cut sub ui_buttons_start { return &theme_ui_buttons_start(@_) if (defined(&theme_ui_buttons_start)); return "\n"; } =head2 ui_buttons_end Returns HTML for the end of a block started by ui_buttons_start. =cut sub ui_buttons_end { return &theme_ui_buttons_end(@_) if (defined(&theme_ui_buttons_end)); return "
    \n"; } =head2 ui_buttons_row(script, button-label, description, [hiddens], [after-submit], [before-submit], [postmethod]) Returns HTML for a button with a description next to it, and perhaps other inputs. The parameters are : =item script - CGI script that this button submits to, like start.cgi. =item button-label - Text to appear on the button. =item description - Text to appear next to the button, describing in more detail what it does. =item hiddens - HTML for hidden fields to include in the form this function generates. =item after-submit - HTML for text or inputs to appear after the submit button. =item before-submit - HTML for text or inputs to appear before the submit button. =item postmethod - Defines the method used to submit the form. Defaults to 'post'. =cut sub ui_buttons_row { return &theme_ui_buttons_row(@_) if (defined(&theme_ui_buttons_row)); my ($script, $label, $desc, $hiddens, $after, $before, $postmethod) = @_; $postmethod ||= 'post'; if (ref($hiddens)) { $hiddens = join("\n", map { &ui_hidden(@$_) } @$hiddens); } return "
    \n". $hiddens. " ". "". ($before ? $before." " : ""). &ui_submit($label).($after ? " ".$after : "")."\n". "". $desc."\n". "
    \n"; } =head2 ui_buttons_hr([title]) Returns HTML for a separator row, for use inside a ui_buttons_start block. =cut sub ui_buttons_hr { my ($title) = @_; return &theme_ui_buttons_hr(@_) if (defined(&theme_ui_buttons_hr)); if ($title) { return "

    $title
    \n"; } else { return "
    \n"; } } ####################### header and footer functions =head2 ui_post_header([subtext]) Returns HTML to appear directly after a standard header() call. This is never called directly - instead, ui_print_header calls it. But it can be overridden by themes. =cut sub ui_post_header { return &theme_ui_post_header(@_) if (defined(&theme_ui_post_header)); my ($text) = @_; my $rv; $rv .= "
    $text
    \n" if (defined($text)); if (!$tconfig{'nohr'} && !$tconfig{'notophr'}) { $rv .= "
    \n"; } return $rv; } =head2 ui_pre_footer Returns HTML to appear directly before a standard footer() call. This is never called directly - instead, ui_print_footer calls it. But it can be overridden by themes. =cut sub ui_pre_footer { return &theme_ui_pre_footer(@_) if (defined(&theme_ui_pre_footer)); my $rv; if (!$tconfig{'nohr'} && !$tconfig{'nobottomhr'}) { $rv .= "\n"; } return $rv; } =head2 ui_print_header(subtext, title, image, [help], [config], [nomodule], [nowebmin], [rightside], [head-stuff], [body-stuff], [below]) Print HTML for a header with the post-header line. The args are the same as those passed to header(), defined in web-lib-funcs.pl, with the addition of the subtext parameter : =item subtext - Text to display below the title =item title - The text to show at the top of the page =item image - An image to show instead of the title text. This is typically left blank. =item help - If set, this is the name of a help page that will be linked to in the title. =item config - If set to 1, the title will contain a link to the module's config page. =item nomodule - If set to 1, there will be no link in the title section to the module's index. =item nowebmin - If set to 1, there will be no link in the title section to the Webmin index. =item rightside - HTML to be shown on the right-hand side of the title. Can contain multiple lines, separated by
    . Typically this is used for links to stop, start or restart servers. =item head-stuff - HTML to be included in the section of the page. =item body-stuff - HTML attributes to be include in the tag. =item below - HTML to be displayed below the title. Typically this is used for application or server version information. =cut sub ui_print_header { &load_theme_library(); return &theme_ui_print_header(@_) if (defined(&theme_ui_print_header)); my ($text, @args) = @_; &header(@args); print &ui_post_header($text); } =head2 ui_print_unbuffered_header(subtext, args...) Like ui_print_header, but ensures that output for this page is not buffered or contained in a table. This should be called by scripts that are producing output while performing some long-running process. =cut sub ui_print_unbuffered_header { my @args = @_; &load_theme_library(); return &theme_ui_print_unbuffered_header(@args) if (defined(&theme_ui_print_unbuffered_header)); $| = 1; $theme_no_table = 1; $args[9] .= " " if ($args[9]); $args[9] .= " data-pagescroll=true"; &ui_print_header(@args); } =head2 ui_print_footer(args...) Print HTML for a footer with the pre-footer line. Args are the same as those passed to footer(). =cut sub ui_print_footer { return &theme_ui_print_footer(@_) if (defined(&theme_ui_print_footer)); my @args = @_; print &ui_pre_footer(); &footer(@args); } =head2 ui_config_link(text, &subs) Returns HTML for a module config link. The first non-null sub will be replaced with the appropriate URL for the module's config page. =cut sub ui_config_link { return &theme_ui_config_link(@_) if (defined(&theme_ui_config_link)); my ($text, $subs) = @_; my $m = &get_module_name(); my @subs = map { $_ || "../config.cgi?$m" } ($subs ? @$subs : ( undef )); return "

    ".&text($text, @subs)."

    \n"; } =head2 ui_print_endpage(text) Prints HTML for an error message followed by a page footer with a link to /, then exits. Good for main page error messages. =cut sub ui_print_endpage { return &theme_ui_print_endpage(@_) if (defined(&theme_ui_print_endpage)); my ($text) = @_; print $text,"

    \n"; &ui_print_footer("/", $text{'index'}); exit; } =head2 ui_subheading(text, ...) Returns HTML for a section heading whose message is the given text strings. =cut sub ui_subheading { return &theme_ui_subheading(@_) if (defined(&theme_ui_subheading)); return "

    ".join("", @_)."

    \n"; } =head2 ui_links_row(&links) Returns HTML for a row of links, like select all / invert selection / add.. Each element of the links array ref should be an HTML fragment like : Create new user =cut sub ui_links_row { return &theme_ui_links_row(@_) if (defined(&theme_ui_links_row)); my ($links) = @_; return @$links ? join("\n|\n", @$links)."
    \n" : ""; } ########################### collapsible section / tab functions =head2 ui_hidden_javascript Returns EOF } =head2 ui_hidden_start(title, name, status, thisurl) Returns HTML for the start of a collapsible hidden section, such as for advanced options. When clicked on, the section header will expand to display whatever is between this function and ui_hidden_end. The parameters are : =item title - Text for the start of this hidden section. =item name - A unique name for this section. =item status - 1 if it should be initially open, 0 if not. =item thisurl - URL of the current page. This is used by themes on devices that don't support Javascript to implement the opening and closing. =cut sub ui_hidden_start { return &theme_ui_hidden_start(@_) if (defined(&theme_ui_hidden_start)); my ($title, $name, $status, $url) = @_; my $rv; if (!$main::ui_hidden_start_donejs++) { $rv .= &ui_hidden_javascript(); } my $divid = "hiddendiv_$name"; my $openerid = "hiddenopener_$name"; my $defimg = $status ? "open.gif" : "closed.gif"; my $defclass = $status ? 'opener_shown' : 'opener_hidden'; $rv .= "*\n"; $rv .= "$title
    \n"; $rv .= "
    \n"; return $rv; } =head2 ui_hidden_end(name) Returns HTML for the end of a hidden section, started by ui_hidden_start. =cut sub ui_hidden_end { return &theme_ui_hidden_end(@_) if (defined(&theme_ui_hidden_end)); my ($name) = @_; return "
    \n"; } =head2 ui_hidden_table_row_start(title, name, status, thisurl) Similar to ui_hidden_start, but for use within a table started with ui_table_start. I recommend against using this where possible, as it can be difficult for some themes to implement. =cut sub ui_hidden_table_row_start { return &theme_ui_hidden_table_row_start(@_) if (defined(&theme_ui_hidden_table_row_start)); my ($title, $name, $status, $url) = @_; my ($rv, $rrv); if (!$main::ui_hidden_start_donejs++) { $rv .= &ui_hidden_javascript(); } my $divid = "hiddendiv_$name"; my $openerid = "hiddenopener_$name"; my $defimg = $status ? "open.gif" : "closed.gif"; my $defclass = $status ? 'opener_shown' : 'opener_hidden'; if ($title) { $rrv .= "\n"; $rrv .= "$title
    \n"; $rv .= &ui_table_row(undef, $rrv, $main::ui_table_cols); } $rv .= "\n"; $rv .= "
    \n"; $rv .= "\n"; return $rv; } =head2 ui_hidden_table_row_end(name) Returns HTML to end a block started by ui_hidden_table_start. =cut sub ui_hidden_table_row_end { return &theme_ui_hidden_table_row_end(@_) if (defined(&theme_ui_hidden_table_row_end)); my ($name) = @_; return "
    \n"; } =head2 ui_hidden_table_start(heading, [tabletags], [cols], name, status, [&default-tds], [rightheading]) Returns HTML for the start of a form block into which labelled inputs can be placed, which is collapsible by clicking on the header. Basically the same as ui_table_start, and must contain HTML generated by ui_table_row. The parameters are : =item heading - Text to show at the top of the form. =item tabletags - HTML attributes to put in the outer
    , typically something like width=100%. =item cols - Desired number of columns for labels and fields. Defaults to 4, but can be 2 for forms with lots of wide inputs. =item name - A unique name for this table. =item status - Set to 1 if initially open, 0 if initially closed. =item default-tds - An optional array reference of HTML attributes for the
    tags in each row of the table. =item right-heading - HTML to appear in the heading, aligned to the right. =cut sub ui_hidden_table_start { return &theme_ui_hidden_table_start(@_) if (defined(&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 $divid = "hiddendiv_$name"; my $openerid = "hiddenopener_$name"; my $defimg = $status ? "open.gif" : "closed.gif"; my $defclass = $status ? 'opener_shown' : 'opener_hidden'; my $text = defined($tconfig{'cs_text'}) ? $tconfig{'cs_text'} : defined($gconfig{'cs_text'}) ? $gconfig{'cs_text'} : "000000"; $rv .= "\n"; my $colspan = 1; if (defined($heading) || defined($rightheading)) { $rv .= ""; } if (defined($rightheading)) { $rv .= ""; $colspan++; } $rv .= "\n"; } $rv .= "
    "; if (defined($heading)) { $rv .= " $heading$rightheading
    \n"; $main::ui_table_cols = $cols || 4; $main::ui_table_pos = 0; $main::ui_table_default_tds = $tds; return $rv; } =head2 ui_hidden_table_end(name) Returns HTML for the end of a form block with hiding, as started by ui_hidden_table_start. =cut sub ui_hidden_table_end { my ($name) = @_; return &theme_ui_hidden_table_end(@_) if (defined(&theme_ui_hidden_table_end)); return "
    \n"; } =head2 ui_tabs_start(&tabs, name, selected, show-border) Returns a row of tabs from which one can be selected, displaying HTML associated with that tab. The parameters are : =item tabs - An array reference of array refs, each of which contains the value and user-visible text for a tab. =item name - Name of the HTML field into which the selected tab will be placed. =item selected - Value for the tab selected by default. =item show-border - Set to 1 if there should be a border around the contents of the tabs. Example code : @tabs = ( [ 'list', 'List services' ], [ 'install', 'Install new service' ] ); print ui_tabs_start(\@tabs, 'mode', 'list'); print ui_tabs_start_tab('mode', 'list'); generate_service_list(); print ui_tabs_end_tab('mode', 'list'); print ui_tabs_start_tab('mode', 'install'); generate_install_form(); print ui_tabs_end_tab('mode', 'install); print ui_tabs_end(); =cut sub ui_tabs_start { return &theme_ui_tabs_start(@_) if (defined(&theme_ui_tabs_start)); my ($tabs, $name, $sel, $border) = @_; my $rv; if (!$main::ui_hidden_start_donejs++) { $rv .= &ui_hidden_javascript(); } # Build list of tab titles and names my $tabnames = "[".join(",", map { "\""."e_escape($_->[0])."\"" } @$tabs)."]"; my $tabtitles = "[".join(",", map { "\""."e_escape($_->[1])."\"" } @$tabs)."]"; $rv .= "\n"; # Output the tabs my $imgdir = "@{[&get_webprefix()]}/images"; $rv .= &ui_hidden($name, $sel)."\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; foreach my $t (@$tabs) { if ($t ne $$tabs[0]) { # Spacer $rv .= "\n"; } my $tabid = "tab_".$t->[0]; $rv .= "\n"; } $rv .= "\n"; $rv .= "
    "; if ($ENV{'HTTP_USER_AGENT'} !~ /msie/i) { # For some reason, the 1-pixel space above the tabs appears huge on IE! $rv .= ""; } $rv .= "
    ". ""; $rv .= ""; if ($t->[0] eq $sel) { # Selected tab $rv .= ""; $rv .= "". " $t->[1] "; $rv .= ""; } else { # Other tab (which has a link) $rv .= ""; $rv .= "". " ". "$t->[1] "; $rv .= ""; $rv .= "\n"; } $rv .= "
    ". "\"\"". "\"\"". "\"\"". "
    "; $rv .= "
    \n"; if ($border) { # All tabs are within a grey box $rv .= "\n"; $rv .= "\n"; $rv .= " \n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= " \n"; $rv .= "
    "; } $main::ui_tabs_selected = $sel; return $rv; } =head2 ui_tabs_end(show-border) Returns HTML to end a block started by ui_tabs_start. The show-border parameter must match the parameter with the same name in the start function. =cut sub ui_tabs_end { return &theme_ui_tabs_end(@_) if (defined(&theme_ui_tabs_end)); my ($border) = @_; my $rv; my $imgdir = "@{[&get_webprefix()]}/images"; if ($border) { $rv .= "
    \n"; } return $rv; } =head2 ui_tabs_start_tab(name, tab) Must be called before outputting the HTML for the named tab, and returns HTML for the required
    block. =cut sub ui_tabs_start_tab { return &theme_ui_tabs_start_tab(@_) if (defined(&theme_ui_tabs_start_tab)); my ($name, $tab) = @_; my $defclass = $tab eq $main::ui_tabs_selected ? 'opener_shown' : 'opener_hidden'; my $rv = "
    \n"; return $rv; } =head2 ui_tabs_start_tabletab(name, tab) Behaves like ui_tabs_start_tab, but for use within a ui_table_start block. I recommend against using this where possible, as it is difficult for themes to implement. =cut sub ui_tabs_start_tabletab { return &theme_ui_tabs_start_tabletab(@_) if (defined(&theme_ui_tabs_start_tabletab)); my $div = &ui_tabs_start_tab(@_); return "
    \n".$div."\n"; } =head2 ui_tabs_end_tab Returns HTML for the end of a block started by ui_tabs_start_tab. =cut sub ui_tabs_end_tab { return &theme_ui_tabs_end_tab(@_) if (defined(&theme_ui_tabs_end_tab)); return "\n"; } =head2 ui_tabs_end_tabletab Returns HTML for the end of a block started by ui_tabs_start_tabletab. =cut sub ui_tabs_end_tabletab { return &theme_ui_tabs_end_tabletab(@_) if (defined(&theme_ui_tabs_end_tabletab)); return "
    \n"; } =head2 ui_max_text_width(width, [text-area?]) Returns a new width for a text field, based on theme settings. For internal use only. =cut sub ui_max_text_width { my ($w, $ta) = @_; my $max = $ta ? $tconfig{'maxareawidth'} : $tconfig{'maxboxwidth'}; return $max && $w > $max ? $max : $w; } ####################### radio hidden functions =head2 ui_radio_selector(&opts, name, selected, [dropdown-mode]) Returns HTML for a set of radio buttons, each of which shows a different block of HTML when selected. The parameters are : =item opts - An array ref to arrays containing [ value, label, html ] =item name - HTML name for the radio buttons =item selected - Value for the initially selected button. =item dropdown - Use a
    tags in the tables. =item tabletags - HTML attributes for the tag. =item title - Optional title to add to the top of the grid. =cut sub ui_grid_table { return &theme_ui_grid_table(@_) if (defined(&theme_ui_grid_table)); my ($elements, $cols, $width, $tds, $tabletags, $title) = @_; return "" if (!@$elements); my $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 .= "
    [$i%$cols]." valign='top' class='ui_grid_cell'>". $elements->[$i]."
    [$i%$cols]." class='ui_grid_cell'>". "
    \n"; if (defined($title)) { $rv = "\n". ($title ? "\n" : ""). "\n". "
    $title
    $rv
    "; } return $rv; } =head2 ui_radio_table(name, selected, &rows, [no-bold]) Returns HTML for a table of radio buttons, each of which has a label and some associated inputs to the right. The parameters are : =item name - Unique name for this table, which is also the radio buttons' name. =item selected - Value for the initially selected radio button. =item rows - Array ref of array refs, one per button. The elements of each are the value for this option, a label, and option additional HTML to appear next to it. =item no-bold - When set to 1, labels in the table will not be bolded =cut sub ui_radio_table { return &theme_ui_radio_table(@_) if (defined(&theme_ui_radio_table)); my ($name, $sel, $rows, $nobold) = @_; return "" if (!@$rows); my $rv = "\n"; foreach my $r (@$rows) { $rv .= "\n"; $rv .= "\n"; if (defined($r->[2])) { $rv .= "\n"; } $rv .= "\n"; } $rv .= "
    [2]) ? "" : " colspan='2'").">". ($nobold ? "" : ""). &ui_oneradio($name, $r->[0], $r->[1], $r->[0] eq $sel, $r->[3]). ($nobold ? "" : ""). "".$r->[2]."
    \n"; return $rv; } =head2 ui_up_down_arrows(uplink, downlink, up-show, down-show) Returns HTML for moving some objects in a table up or down. The parameters are : =item uplink - URL for the up-arrow link. =item downlink - URL for the down-arrow link. =item up-show - Set to 1 if the up-arrow should be shown, 0 if not. =item down-show - Set to 1 if the down-arrow should be shown, 0 if not. =item up-icon - Optional path to icon for up link =item down-icon - Optional path to icon for down link =cut sub ui_up_down_arrows { return &theme_ui_up_down_arrows(@_) if (defined(&theme_ui_up_down_arrows)); my ($uplink, $downlink, $upshow, $downshow, $upicon, $downicon) = @_; my $mover; my $imgdir = "@{[&get_webprefix()]}/images"; $upicon ||= "$imgdir/moveup.gif"; $downicon ||= "$imgdir/movedown.gif"; if ($downshow) { $mover .= "". ""; } else { $mover .= ""; } if ($upshow) { $mover .= "". ""; } else { $mover .= ""; } return $mover; } =head2 ui_hr Returns a horizontal row tag, typically just an
    =item tags - Additional HTML attributes for the
    tag. =cut sub ui_hr { return &theme_ui_hr(@_) if (defined(&theme_ui_hr)); my ($tags) = @_; return "
    \n"; } =head2 ui_nav_link(direction, url, disabled) Returns an arrow icon linking to the provided url. =cut sub ui_nav_link { return &theme_ui_nav_link(@_) if (defined(&theme_ui_nav_link)); my ($direction, $url, $disabled) = @_; my $alt = $direction eq "left" ? '<-' : '->'; if ($disabled) { return "\"$alt\"\n"; } else { return "\"$alt\"\n"; } } =head2 ui_confirmation_form(cgi, message, &hiddens, [&buttons], [otherinputs], [extra-warning]) Returns HTML for a form asking for confirmation before performing some action, such as deleting a user. The parameters are : =item cgi - Script to which the confirmation form submits, like delete.cgi. =item message - Warning message for the user to see. =item hiddens - Array ref of two-element array refs, containing hidden form field names and values. =item buttons - Array ref of two-element array refs, containing form button names and labels. =item otheirinputs - HTML for extra inputs to include in their form. =item extra-warning - An additional separate warning message to show. =cut sub ui_confirmation_form { my ($cgi, $message, $hiddens, $buttons, $others, $warning) = @_; my $rv; $rv .= "
    \n"; $rv .= &ui_form_start($cgi, "post"); foreach my $h (@$hiddens) { $rv .= &ui_hidden(@$h); } $rv .= "$message

    \n"; if ($warning) { $rv .= "$warning

    \n"; } if ($others) { $rv .= $others."

    \n"; } $rv .= &ui_form_end($buttons); $rv .= "

    \n"; return $rv; } =head2 ui_text_color(text, type) Returns HTML for a text string, with its color determined by $type. =item text - contains any text string =item type - returned text color =cut sub ui_text_color { my ($text, $type) = @_; my ($color); if (defined (&theme_ui_text_color)) { return &theme_ui_text_color(@_); } if ($type eq "success") { $color = "#3c763d"; } elsif ($type eq "info") { $color = "#31708f"; } elsif ($type eq "warn") { $color = "#8a6d3b"; } elsif ($type eq "danger") { $color = "#a94442"; } return "$text"; } =head2 ui_alert_box(msg, type) Returns HTML for an alert box, with background color determined by $type. $msg contains any text or HTML to be contained within the alert box, and can include forms. Type of alert: =item success - green =item info - blue =item warn - yellow =item danger - red =cut sub ui_alert_box { my ($msg, $type) = @_; my ($rv, $color); if (defined (&theme_ui_alert_box)) { return &theme_ui_alert_box(@_); } if ($type eq "success") { $color = "DFF0D8"; } elsif ($type eq "info") { $color = "D9EDF7"; } elsif ($type eq "warn") { $color = "FCF8E3"; } elsif ($type eq "danger") { $color = "F2DEDE"; } $rv .= "

    \n"; $rv .= "$msg\n"; $rv .= "

    \n"; return $rv; } ####################### javascript functions =head2 js_disable_inputs(&disable-inputs, &enable-inputs, [tag]) Returns Javascript to disable some form elements and enable others. Mainly for internal use. =cut sub js_disable_inputs { my $rv; my $f; foreach $f (@{$_[0]}) { $rv .= "e = form.elements[\"$f\"]; e.disabled = true; "; $rv .= "for(i=0; i= 0) { # When enabling both a _def field and its associated text field, # disable the text if the _def is set to 1 my $tf = $1; $rv .= "e = form.elements[\"$f\"]; for(i=0; i 5) { if ($farleft) { $rv .= "". "\n"; } else { $rv .= "\n"; } } # Left link if ($left) { $rv .= "". "\n"; } else { $rv .= "\n"; } # Message and inputs $rv .= $msg; $rv .= " ".$inputs if ($inputs); # Right link if ($right) { $rv .= "". "\n"; } else { $rv .= "\n"; } # Far right link, if needed if (@_ > 5) { if ($farright) { $rv .= "". "\n"; } else { $rv .= "\n"; } } $rv .= "
    ".$below if ($below); $rv .= &ui_form_end() if ($cgi); $rv .= "\n"; return $rv; } =head2 js_checkbox_disable(name, &checked-disable, &checked-enable, [tag]) For internal use only. =cut sub js_checkbox_disable { my $rv; my $f; foreach $f (@{$_[1]}) { $rv .= "form.elements[\"$f\"].disabled = $_[0].checked; "; } foreach $f (@{$_[2]}) { $rv .= "form.elements[\"$f\"].disabled = !$_[0].checked; "; } return $_[3] ? "$_[3]='$rv'" : $rv; } =head2 js_redirect(url, [window-object], [timeout]) Returns HTML to trigger a redirect to some URL. =cut sub js_redirect { my ($url, $window, $timeout) = @_; if (defined(&theme_js_redirect)) { return &theme_js_redirect(@_); } $window ||= "window"; $timeout ||= 0; if ($url =~ /^\//) { # If the URL is like /foo , add webprefix $url = &get_webprefix().$url; } return ""; } =head2 ui_webmin_link(module, page) Returns the URL for a link to this Webmin instance that can be used in an email =cut sub ui_webmin_link { my ($mod, $page) = @_; if (defined(&theme_ui_webmin_link)) { return &theme_ui_webmin_link(@_); } my %miniserv; &get_miniserv_config(\%miniserv); my $proto = $miniserv{'ssl'} ? 'https' : 'http'; my $port = $miniserv{'port'}; my $host = $ENV{'HTTP_HOST'} || &get_display_hostname(); if ($host =~ /^([a-zA-Z0-9\-\_\.]+):(\d+)$/) { $host = $1; $port = $2; } my $rv = $proto."://$host:$port"; if ($mod) { $rv .= "/$mod"; } if ($page) { $rv .= "/$page"; } return $rv; } =head2 ui_line_break_double() Create double line break, with accessible second break =cut sub ui_line_break_double { if (defined(&theme_ui_line_break_double)) { return &theme_ui_line_break_double(@_); } return "

    \n"; } =head2 ui_page_refresh() Returns theme based JavaScript function to refresh current page =cut sub ui_page_refresh { if (defined(&theme_ui_page_refresh)) { return &theme_ui_page_refresh(@_); } return "window.location.reload()"; } =head2 ui_details(Config, Opened) Creates a disclosure widget in which information is visible only when the widget is toggled into an "open" state. =cut sub ui_details { my ($c, $o) = @_; if (defined(&theme_ui_details)) { return &theme_ui_details(@_); } my $rv; if (!$c->{'html'}) { $c->{'title'} = &html_escape($c->{'title'}, 1); $c->{'content'} = &html_escape($c->{'content'}, 1); } $c->{'class'} = " class=\"@{["e_escape($c->{'class'})]}\"" if($c->{'class'}); $o = ' open' if ($o); $rv = "{'class'}$o>"; $rv .= "

    $c->{'title'}"; $rv .= "$c->{'content'}"; $rv .= ""; return $rv; } =head2 ui_div_row(label, content) Prints a row without using a table and places label and content in a way ui_table_row does =cut sub ui_div_row { if (defined(&theme_ui_div_row)) { return &theme_ui_div_row(@_); } my ($label, $content) = @_; return "
    $label$content
    \n"; } =head2 ui_space(number) Prints no breakable space number of given times =cut sub ui_space { if (defined(&theme_ui_space)) { return &theme_ui_space(@_); } my ($number) = @_; $number ||= 1; return "".(" " x $number)."\n"; } =head2 ui_newline(number) Prints new lines given number of times =cut sub ui_newline { if (defined(&theme_ui_newline)) { return &theme_ui_newline(@_); } my ($number) = @_; $number ||= 1; return "".("
    " x $number)."
    \n"; } =head2 ui_text_wrap(text) Wraps any text into span tags =cut sub ui_text_wrap { if (defined(&theme_ui_text_wrap)) { return &theme_ui_text_wrap(@_); } my ($text) = @_; return "$text\n"; } =head2 ui_element_inline(text) Wraps any text into span tags =cut sub ui_element_inline { if (defined(&theme_ui_element_inline)) { return &theme_ui_element_inline(@_); } my ($element, $type) = @_; return "$element\n"; } =head2 ui_paginations(&array, &opts) Given an array reference, slice it and return hash reference with pagination buttons, search form and other elements to be inserted on the page =cut sub ui_paginations { return &theme_ui_paginations(@_) if (defined(&theme_ui_paginations)); my ($arr, $opts) = @_; my %rv; my $id = $main::ui_paginations++; my @arr = @{$arr}; my ($script_name) = $0 =~ /([^\/]*\.cgi)$/; my $top_offset_px = int($opts->{'paginations'}->{'offset'}->{'top'}) || 105; my $bottom_offset_px = int($opts->{'paginations'}->{'offset'}->{'bottom'}) || 90; my $row_size_px = int($opts->{'paginations'}->{'offset'}->{'row'}) || 24; my $items_per_page = int($tconfig{'paginate'}) || int($opts->{"paginate${id}"}) || 20; my $curent_page = int($opts->{"page${id}"}) || 1; my $search_term = &un_urlize($opts->{"search${id}"}); my $pagination_target = $opts->{'paginations'}->{'paginator'}->{'target'} || $script_name; my $paginator_wrap_class = $opts->{'paginations'}->{'paginator'}->{'class'}->{'wrap'} || "ui_form_elements_wrapper_paginator"; my $link_page_cls = $opts->{'paginations'}->{'paginator'}->{'class'}->{'links'} || 'ui_link_pagination'; my $link_search_cls = $opts->{'paginations'}->{'paginator'}->{'class'}->{'textbox'} || 'ui_textbox_pagination'; my $text_showing_cls = $opts->{'paginations'}->{'paginator'}->{'class'}->{'text'} || 'ui_showing_items'; my $search_target = $opts->{'paginations'}->{'search'}->{'target'} || $script_name; my $search_wrap_class = $opts->{'paginations'}->{'search'}->{'class'}->{'wrap'} || "ui_form_elements_wrapper_search"; my $search_placeholder = $opts->{'paginations'}->{'search'}->{'placeholder'} || $text{'ui_searchok'}; my $exported_form = $opts->{'paginations'}->{'form'}; my $ui_column_colspan = int($exported_form->{'colspan'} || 4); # If we have a search string filter existing content if (ref($arr) eq 'ARRAY' && $arr->[0]) { if ($search_term) { my @sarr; map { if (ref($_) eq 'ARRAY') { arr: for (my $i = 0; $i <= $#$_; $i++) { push(@sarr, $_), last arr if(index(lc($_->[$i]), lc($search_term)) != -1); } } if (ref($_) eq 'HASH') { hash: foreach my $__ (values %{$_}) { push(@sarr, $_), last hash if(index(lc($__), lc($search_term)) != -1); } } } @arr; @arr = @sarr; } # Can pagination be done automatically # depending on the client screen height? my $items_per_page_client = int($opts->{'client_height'} || get_http_cookie('client_height')); my $items_per_page = $tconfig{'paginate'} ? $items_per_page : (int($ENV{'HTTP_X_CLIENT_PAGINATE'}) || ($items_per_page_client ? ((int(($items_per_page_client - $top_offset_px - $bottom_offset_px) / $row_size_px))) : $items_per_page)); # If caller wants specific pagination number, # e.g. a module config, use that instead if ($exported_form && $exported_form->{'paginate'}) { $items_per_page = $exported_form->{'paginate'}; } # Sanity check for minimum items per page if ($items_per_page <= 0) { $items_per_page = 2; } # Pagination my $totals_items_original = scalar(@arr); my $total_pages = ceil(($totals_items_original) / $items_per_page); my $total_pages_length = length($total_pages); # Dynamically parse external form elements into query string my $exported_form_query = ""; if ($exported_form) { foreach (keys %{$exported_form}) { $exported_form_query .= "$_=@{[&urlize($exported_form->{$_})]}&"; } $exported_form_query =~ s/\&$//; } # Return pagination jumper only # if there is more than one page if ($total_pages > 1) { my $totals_items_spliced = $totals_items_original; my $start_page_with = $curent_page * $items_per_page; $curent_page = $total_pages if ($curent_page > $total_pages); $curent_page = 1 if ($curent_page <= 0); my $curent_page_prev = $curent_page - 1; my $page_prev_disabled = $curent_page_prev <= 0 ? " disabled" : ""; my $curent_page_next = $curent_page + 1; my $page_next_disabled = $curent_page_next > $total_pages ? " disabled" : ""; my $splice_start = $items_per_page * $curent_page_prev; my $splice_end = $items_per_page; @arr = splice(@arr, $splice_start, $splice_end); $totals_items_spliced = scalar(@arr); # # Pagination jumper # my $paginator_id = 'paginator-form'; my $paginator_data = "$paginator_id-data"; # Paginator form $rv{'paginator'}->{'form'} = &ui_form_start($pagination_target, 'get', undef, "id='$paginator_id${id}'"); $rv{'paginator'}->{'form'} .= &ui_form_end(); # Paginator form data $rv{'paginator'}->{'form-data'} = &ui_hidden("search${id}", $search_term, "$paginator_id${id}") if ($search_term); $rv{'paginator'}->{'form-data'} .= &ui_hidden("paginate${id}", $items_per_page, "$paginator_id${id}"); # Calculate showing start and range numbers my $current_showing_start = $curent_page == 1 ? 1 : int(($items_per_page * $curent_page + 1) - $items_per_page); my $current_showing_range = int($current_showing_start + $items_per_page > $totals_items_original ? $totals_items_original : $current_showing_start + $items_per_page - 1); # Showing items range selector text $rv{'paginator'}->{'form-data'} .= "@{[ &text('paginator_showing_start', $current_showing_start, $current_showing_range, $totals_items_original) ]} "; # # Arrow links # my $search_term_urlize = &urlize($search_term); my $curent_page_prev_urlize = &urlize($curent_page_prev); my $curent_page_next_urlize = &urlize($curent_page_next); my $items_per_page_urlize = &urlize($items_per_page); my $total_pages_html_escape = &html_escape($total_pages); # Arrow link left $rv{'paginator'}->{'form-data'} .= &ui_link("$pagination_target?page${id}=$curent_page_prev_urlize". "&search${id}=$search_term_urlize&paginate${id}=$items_per_page_urlize".'&'."$exported_form_query", ' ⏴ ', "@{[&html_escape($link_page_cls)]} @{[&html_escape($link_page_cls)]}_left$page_prev_disabled", "data-formid='$id'"); # Page number input selector $rv{'paginator'}->{'form-data'} .= &ui_textbox("page${id}", $curent_page, $total_pages_length, undef, $total_pages_length, "data-class='@{["e_escape($link_search_cls)]}' form='$paginator_id${id}'"); # Out of pages text $rv{'paginator'}->{'form-data'} .= " @{[&text('paginator_showing_end', $total_pages_html_escape)]}"; # Arrow link right $rv{'paginator'}->{'form-data'} .= &ui_link("$pagination_target?page${id}=$curent_page_next_urlize". "&search${id}=$search_term_urlize&paginate${id}=$items_per_page_urlize".'&'."$exported_form_query", ' ⏵ ', "@{[&html_escape($link_page_cls)]} @{[&html_escape($link_page_cls)]}_right$page_next_disabled", "data-formid='$id'"); # Allow listing pages using "Alt + left/right" hotkeys if (!$ENV{'HTTP_X_CLIENT_PAGINATE_NO_SCRIPT'}) { $rv{'paginator'}->{'form-scripts'} .= ""; } # Dynamically adding external form elements if ($exported_form) { foreach (keys %{$exported_form}) { $rv{'paginator'}->{'form-data'} .= &ui_hidden($_, $exported_form->{$_}, "$paginator_id${id}"); } } $rv{'paginator'}->{'form-data'} = &ui_form_elements_wrapper($rv{'paginator'}->{'form-data'}, "$paginator_id${id}", "e_escape($paginator_wrap_class)) } # # Search form # if ($total_pages > 1 || $search_term) { my $search_id = 'search-form'; my $search_data = "$search_id-data"; # Paginator search form $rv{'search'}->{'form'} = &ui_form_start($search_target, 'get', undef, "id='$search_id${id}'"); $rv{'search'}->{'form'} .= &ui_form_end(); # Paginator search form data $rv{'search'}->{'form-data'} .= &ui_hidden("paginate${id}", $items_per_page, "$search_id${id}"); $rv{'search'}->{'form-data'} .= &ui_hidden("page${id}", 1, "$search_id${id}"); my $search_placeholder_length = length($search_term) || length($search_placeholder); $search_placeholder_length = $search_placeholder_length < 8 ? 8 : $search_placeholder_length; $search_placeholder_length = 24 if ($search_placeholder_length >= 24); # Search box $rv{'search'}->{'form-data'} .= &ui_textbox("search${id}", $search_term, $search_placeholder_length, undef, undef, "data-class='@{["e_escape($link_search_cls)]}_search' ". "placeholder='@{["e_escape($search_placeholder)]}' form='$search_id${id}'"); # Search reset using JS $rv{'search'}->{'form-data'} .= &ui_reset('⛌', undef, "onclick='document.getElementById(\"$search_id${id}\").search${id}.value = \"\";". "document.getElementById(\"$search_id${id}\").submit()'"); # Dynamically adding external form elements if ($exported_form) { foreach (keys %{$exported_form}) { $rv{'search'}->{'form-data'} .= &ui_hidden($_, $exported_form->{$_}, "$search_id${id}"); } } $rv{'search'}->{'form-data'} = &ui_form_elements_wrapper($rv{'search'}->{'form-data'}, "$search_id${id}", "e_escape($search_wrap_class)); # Search no results $rv{'search'}->{'no-results'} = &ui_columns_row([&text('paginator_nosearchrs', &html_escape($search_term))], ['colspan="'.$ui_column_colspan.'" align="center"']); } # Elements for the parent form, so after submission to # make sure that we return to the right paginated page $rv{'form'} = &ui_hidden("page${id}", $curent_page). &ui_hidden("paginate${id}", $items_per_page). &ui_hidden("search${id}", $search_term); } @$arr = @arr; return \%rv; } =head2 ui_hide_outside_of_viewport(elem) Prints element if not in viewport. Useful when printing top and bottom controls as printing bottom controls only if it's not in view already =cut sub ui_hide_outside_of_viewport { my ($elem_sel) = @_; $elem_sel ||= "[data-outside-of-viewport]"; if (defined(&theme_ui_hide_outside_of_viewport)) { return &theme_ui_hide_outside_of_viewport(@_); } return < try { (function() { var elems = document.querySelectorAll('$elem_sel'), i; for (i = 0; i < elems.length; i++) { if (elems[i].offsetTop < window.innerHeight) { elems[i].style.display = "none"; } } })(); } catch (e) {}; EOF } =head2 ui_read_file_contents_limit(\%data) Reads file content with options and returns head and/or tail separated with chomped message =cut sub ui_read_file_contents_limit { if (defined(&theme_ui_read_file_contents_limit)) { return &theme_ui_read_file_contents_limit(@_); } my ($opts) = @_; my $binary = -s $opts->{'file'} >= 128 && -B $opts->{'file'}; my $data = &read_file_contents_limit($opts->{'file'}, $opts->{'limit'}, $opts); my $error = $data->{'error'}; if ($error) { return $error; } my $nonulls = sub { $_[0] =~ s/[^[:print:]\n\r\t]/\ /g; return $_[0]; }; my $head = $data->{'head'}; my $tail = $data->{'tail'}; my $chomped = $data->{'chomped'}; my $fsize = $data->{'size'}; my $flimit = $data->{'limit'}; my $msg_type = !$head && $tail ? '_tail' : $head && !$tail ? '_head' : undef; my $nlines = $nslines = $nelines = "\n" x 10; $nslines = undef if (!$head); $nelines = undef if (!$tail); my $chomped_msg; $chomped_msg = "${nslines}[--- @{[&text(\"file_truncated_message$msg_type\", &nice_size($flimit), &nice_size($chomped), &nice_size($fsize))]} ---]$nelines" if ($chomped); # Trim nulls $head = &$nonulls($head) if ($binary && $head); $tail = &$nonulls($tail) if ($binary && $tail); # Return data if ($head && $tail) { return $head . $chomped_msg . $tail; } if ($tail) { return $chomped_msg . $tail; } if ($head) { return $head . $chomped_msg; } } =head2 ui_note(text) Returns a note as a small font size text =cut sub ui_note { return &theme_ui_note(@_) if (defined(&theme_ui_note)); my ($text) = @_; return "  ⓘ  ". "$text"; } =head2 ui_brh() Returns a break line with ability to style height =cut sub ui_brh { return &theme_ui_brh() if (defined(&theme_ui_brh)); return "
    \n"; } # ui_tag_start(tag, [attrs], [no-new-line]) # Function to create an opening HTML tag with optional attributes. # Attributes are passed as a hash reference and its values are quote escaped. sub ui_tag_start { return theme_ui_tag_start(@_) if (defined(&theme_ui_tag_start)); my ($tag, $attrs, $nnl) = @_; # Ensure every tag gets a proper marker class $attrs ||= {}; $attrs->{'class'} = defined($attrs->{class}) ? "ui--$tag $attrs->{class}" : "ui--$tag"; # Start building tag my $rv = "<$tag"; # Add attributes if provided if ($attrs && ref($attrs) eq 'HASH') { foreach my $key (keys %$attrs) { my $value = $attrs->{$key}; if (defined($value)) { $value = "e_escape($value, '"'); $value =~ tr/\n\t//d; $value =~ s/\s+/ /g; $rv .= " $key=\"$value\"" ; } elsif ($key) { $rv .= " $key"; } } } # Close the opening tag $rv .= $nnl ? ">" : ">\n"; # Handle special case for tag $rv = "\n$rv" if ($tag eq 'html'); return $rv; } # ui_tag_content(content) # Function to handle the content of an HTML tag. sub ui_tag_content { return theme_ui_tag_content(@_) if (defined(&theme_ui_tag_content)); my ($content) = @_; my $rv; $rv = $content if (defined($content)); return $rv; } # ui_tag_end(tag) # Function to create a closing HTML tag. sub ui_tag_end { return theme_ui_tag_end(@_) if (defined(&theme_ui_tag_end)); my ($tag) = @_; return "\n"; } # ui_tag(tag, [content], [attrs]) # Function to create a complete HTML tag with optional content and attributes. sub ui_tag { return theme_ui_tag(@_) if (defined(&theme_ui_tag)); my ($tag, $content, $attrs) = @_; my $rv = ui_tag_start($tag, $attrs, !defined($content)); $rv .= ui_tag_content($content) if (defined($content)); my %void_tags = map { $_ => 1 } qw( area base br col embed hr img input link meta param source track wbr ); $rv .= ui_tag_end($tag) if (!exists($void_tags{lc($tag)})); return $rv; } # ui_alert(content, type, [icon], [attrs]) # Generates an HTML alert with the specified content, type, and optional icon # and attributes. # # Parameters: # content - The main message/body of the alert # type - Alert style: "success", "info", "warning", "danger", "danger-fatal" # icon - Optional. Controls icon and title display: # - If undefined: uses default icon and title for the alert type # - If string: uses as icon class with default title # - If array ref [icon, title, no_break]: # - icon: Icon class # - title: Custom title (if undef, uses default for type) # - no_break: If 1, no line break after title (space instead) # attrs - Optional hash ref of additional HTML attributes for the alert div # # Examples: # ui_alert("Operation completed", "success"); # ui_alert("Access denied", "danger", "fa-lock"); # ui_alert("Settings changed", "info", ["fa-info-circle", "", 1]); # ui_alert("Server offline", "warning", undef, {id => "server-status"}); sub ui_alert { return theme_ui_alert(@_) if (defined(&theme_ui_alert)); my ($content, $type, $icon, $attrs) = @_; # Default alert type $type ||= 'info'; # Default icons and titles based on type my %type_defaults = ( 'success' => { 'icon' => 'fa-check-circle', 'title' => $text{'ui_success'} }, 'info' => { 'icon' => 'fa-info-circle', 'title' => $text{'ui_info'} }, 'warning' => { 'icon' => 'fa-exclamation-triangle', 'title' => $text{'ui_warning'} }, 'danger' => { 'icon' => 'fa-bolt', 'title' => $text{'ui_error'} }, 'danger-fatal' => { 'icon' => 'fa-exclamation-triangle', 'title' => $text{'ui_error_fatal'} } ); my $use_icon = ''; my $use_title = ''; my $use_br = 1; # Default to using line break # Process icon parameter if (!defined($icon)) { # Use defaults based on type if ($type_defaults{$type}) { $use_icon = $type_defaults{$type}{'icon'}; $use_title = $type_defaults{$type}{'title'}; } } elsif (ref($icon)) { # Array format [icon_class, title, no_br] if (defined($icon->[0])) { $use_icon = $icon->[0]; } else { $use_icon = $type_defaults{$type}{'icon'}; } # Title: if provided use it, else use default for type if (defined($icon->[1])) { $use_title = $icon->[1]; } elsif ($type_defaults{$type}) { $use_title = $type_defaults{$type}{'title'}; } # Line break flag: 1 = no break, anything else = break $use_br = $icon->[2] ? 0 : 1 if (defined($icon->[2])); } else { # String format: just the icon class $use_icon = $icon; $use_title = $type_defaults{$type} ? $type_defaults{$type}{'title'} : ''; } # Prepare attributes for the alert div my $all_attrs = $attrs || {}; # Add alert class my $class = 'alert'; $class .= ' alert-'.$type if ($type); $all_attrs->{'class'} = $all_attrs->{'class'} ? "$class $all_attrs->{'class'}" : $class; # Build alert my $rv = ''; # Start alert container $rv .= ui_tag_start('div', $all_attrs); # Add icon and title if either is available if ($use_icon || $use_title) { # Add icon if available if ($use_icon) { $rv .= ui_tag('i', undef, { 'class' => "fa fa-fw $use_icon" }); $rv .= ' '; } # Add title if available if ($use_title) { $rv .= ui_tag('strong', $use_title); } # Add line break if needed if ($use_br) { $rv .= '
    '; } else { $rv .= ' '; } $rv .= "\n"; } # Add main content $rv .= ui_tag_content(ui_tag('span', $content)); # Close alert container $rv .= ui_tag_end('div'); return $rv; } # ui_button_icon(text, icon, [attrs]) # Creates a button with an icon and text # Parameters: # text - The text/label for the button # icon - Icon class # attrs - Optional hash ref of additional HTML attributes # # Examples: # ui_button_icon("Save", "save", {class => "primary"}) # ui_button_icon("Delete", "trash", {type => "submit", name => "delete"}) sub ui_button_icon { return theme_ui_button_icon(@_) if (defined(&theme_ui_button_icon)); my ($text, $icon, $attrs) = @_; # Default to button type if not specified my $all_attrs = $attrs || {}; $all_attrs->{'type'} ||= 'button'; # Button class my $btn_cls = $all_attrs->{'class'}; $all_attrs->{'class'} = "btn " . ($btn_cls ? ($btn_cls =~ /^btn-/ ? $btn_cls : "btn-$btn_cls") : 'btn-default'); # Build the button my $rv = ui_tag_start('button', $all_attrs); # Add icon if specified if ($icon) { my $icon_class = ""; # Check if icon specifies a specific bundle (fa2) if ($icon =~ /^fa2-/) { $icon_class = "fa2 $icon"; } # Check if it already has fa- prefix elsif ($icon =~ /^fa-/) { $icon_class = "fa $icon"; } # Otherwise add the default fa- prefix else { $icon_class = "fa fa-$icon"; } $rv .= ui_tag('i', undef, {'class' => $icon_class}); $rv .= "  "; } # Add text $rv .= ui_tag_content($text) if defined($text); # Close the button $rv .= ui_tag_end('button'); return $rv; } # ui_link_icon(href, text, [icon], [attrs]) # Creates a link with an icon and text # Parameters: # href - The URL for the link # text - The text/label for the link # icon - Icon class # attrs - Optional hash ref of additional HTML attributes # # Examples: # ui_link_icon("view.cgi?id=1", "View Details", "eye", {class => "primary"}) # ui_link_icon("docs.html", "Documentation", "book", {target => "_blank"}) sub ui_link_icon { return theme_ui_link_icon(@_) if (defined(&theme_ui_link_icon)); my ($href, $text, $icon, $attrs) = @_; # Create attribute hash and set href my $all_attrs = $attrs || {}; $all_attrs->{'href'} = $href if (defined($href)); # Button class my $btn_cls = $all_attrs->{'class'}; $all_attrs->{'class'} = "btn " . ($btn_cls ? ($btn_cls =~ /^btn-/ ? $btn_cls : "btn-$btn_cls") : 'btn-default'); # Build the link my $rv = ui_tag_start('a', $all_attrs); # Add icon if specified if ($icon) { my $icon_class = ""; # Check if icon specifies a specific bundle (fa2) if ($icon =~ /^fa2-/) { $icon_class = "fa2 $icon"; } # Check if it already has fa- prefix elsif ($icon =~ /^fa-/) { $icon_class = "fa $icon"; } # Otherwise add the default fa- prefix else { $icon_class = "fa fa-$icon"; } $rv .= ui_tag('i', undef, {'class' => $icon_class}); $rv .= "  "; } # Add text $rv .= ui_tag_content($text) if (defined($text)); # Close the link $rv .= ui_tag_end('a'); return $rv; } # ui_icon(icon, [attrs]) # Creates an icon element # Parameters: # icon - Icon class (with or without fa- prefix) # attrs - Optional hash ref of additional HTML attributes # # Examples: # ui_icon("search") # Standard icon # ui_icon("fa2-warning") # Extended icon set sub ui_icon { return theme_ui_icon(@_) if (defined(&theme_ui_icon)); my ($icon, $attrs) = @_; return "" if (!defined($icon)) || $icon eq ''; # Create attribute hash my $all_attrs = $attrs || {}; # Process icon class my $icon_class = ""; # Check if icon is in a specific bundle if ($icon =~ /^fa2-/) { $icon_class = "fa2 $icon"; } elsif ($icon =~ /^fa-/) { $icon_class = "fa $icon"; } else { $icon_class = "fa fa-$icon"; } # Make icon always fixed width unless specified otherwise $icon_class .= " fa-fw" if ($all_attrs->{'class'} !~ /fa-dw/); # Add icon class to any existing classes if ($all_attrs->{'class'}) { $all_attrs->{'class'} .= " $icon_class"; } else { $all_attrs->{'class'} = $icon_class; } # Build the icon tag return ui_tag('i', undef, $all_attrs); } # ui_br([attrs]) # Creates a line break element sub ui_br { return theme_ui_br(@_) if (defined(&theme_ui_br)); my ($attrs) = @_; return ui_tag('br', undef, $attrs); } # ui_p(content, [attrs]) # Creates a paragraph element with optional content sub ui_p { return theme_ui_p(@_) if (defined(&theme_ui_p)); my ($content, $attrs) = @_; return ui_tag('p', $content, $attrs); } =head2 ui_text_mask(text, [tag], [extra_class]) Returns an HTML string with the given text hidden inside a tag that only shows on hover. If a second parameter is given, it is used as the outer tag that triggers the hover (default is "td"). If a third parameter is provided, it is added as an extra class to both the tag and its style. =cut sub ui_text_mask { return &theme_ui_text_mask(@_) if (defined(&theme_ui_text_mask)); my ($text, $tag, $extra_class) = @_; my $class = 'hover-mask'; my $classcss = ".$class"; if ($extra_class) { $class .= " $extra_class"; $classcss .= ".$extra_class"; } $tag ||= 'td'; my $style_content = <<"CSS"; x-ui-text-mask${classcss} { position: relative; display: inline-block; color: transparent; transition:color .25s ease; } x-ui-text-mask${classcss}::after{ content: attr(data-mask); position: absolute; inset: 0; color: var(--ui-password-mask-color, #000); pointer-events: none; transition: opacity .25s ease; } $tag:has(>*>x-ui-text-mask${classcss}):hover x-ui-text-mask${classcss}, $tag:has(>x-ui-text-mask${classcss}):hover x-ui-text-mask${classcss}{ color: inherit; } $tag:has(>*>x-ui-text-mask${classcss}):hover x-ui-text-mask${classcss}::after, $tag:has(>x-ui-text-mask${classcss}):hover x-ui-text-mask${classcss}::after{ opacity: 0; } CSS my $rv = ''; $rv .= &ui_tag('style', $style_content, { type => 'text/css' }) if (!$main::ui_text_mask_donecss->{"$tag$class"}++); $rv .= &ui_tag('x-ui-text-mask', $text, { 'class' => $class, 'data-mask' => '••••••••' }); return $rv; } 1;