Scripts Library

From Koha Wiki
Jump to navigation Jump to search

This page includes scripts in different languages that can enhance, solve or automate some specific tasks. Note that code below has not been tested by koha developpers and should be read/test thoroughly before applying to your own system. Scripts in any code language can be added to this list.


Use them at your own risks!

Scripts Template

Script title

  • Developer: Name of script developer
  • Module: OPAC / STAFF / BOTH / Command line / NA
  • Purpose: Purpose of the script
  • Requirements: Any specific requirements or dependencies the script needs to run
  • Coding Language: The coding language the script is written in.
  • Instructions: Any specific instructions needed.
#!/usr/bin/perl
my code
...
...

Scripts

Send birthday greetings to patrons

  • Developer: Alvaro Cornejo
  • Module: NA - command line script
  • Purpose: This script, when placed into a cronjob, will send a greetings email to patrons on their birthdate
  • Requirements: a public report, set a cronjob
  • Coding Language: perl
  • Instructions: This script needs a public report that will pass patron info to script. Its report id shall be replaced on the script. The LIMIT 50 on the SQL can be anything. It is just there to override "hardcoded" records limit -10- on public reports.
#To run script manually: 

/usr/bin/perl /PATHTOSCRIPT/koha_birthdays.pl

# To have crontab run the script everyday at 6am add:

0 6 * * * USER /usr/bin/perl /PATHTOSCRIPT/koha_birthdays.pl

The birthday script:

#!/usr/bin/perl
# Library includes
  use strict;
  use warnings;
  use LWP::UserAgent;
  use HTTP::Request; 
  use JSON;
 
#My Global Variables
  my $hello = "";
  my $subject = "";
  my $body = "";
  my $message = "";
  my $to = "";
  my $from = "";
  my $result = "";
  my $i = 0;
 
# Capture report data:
# Fields from report: surname, firstname, email, emailpro, sex, lang
# Prepare connection, call url and insert data (json format) to arrary
  my $ua = new LWP::UserAgent;
  $ua->agent("Perl API Client/1.0");
  # Replace YOUROPACBASEDOMAIN with the base url of your OPAC
  # Replace XX with your public report ID
  my $url = "https://YOUROPACBASEDOMAIN/cgi-bin/koha/svc/report?id=XX"; 
  my $request = HTTP::Request->new("GET" => $url);
  my $response = $ua->request($request);
  my $json_obj = JSON->new->utf8->decode($response->content);
  my $row_num = scalar(@{$json_obj});
  
#Scroll/split each registry and send the email
  foreach my $row (@{$json_obj}) {
     if ($i < $row_num) {
         my $surname = @{$row}[0];
         my $firstname = @{$row}[1];
         my $email = @{$row}[2];
         my $emailpro = @{$row}[3];
         my $sex = @{$row}[4];
         my $lang = @{$row}[5];

         # Get destination address (private else professional)
         if ($email) {
            $to = $email;
         } elsif ($emailpro) {
            $to = $emailpro;
         } else {
           next;
         }

         # Assemble the patron salutation string for each language. Our default is es-ES
         if ($lang eq "default" || $lang eq "es-ES") {
            if ($sex eq "M") {
               $hello = "Estimado Sr. $surname,\n"
            } elsif ($sex eq "F") {
               $hello = "Estimada Sra. $surname,\n"
            } else {
               $hello = "Estimado(a) $firstname $surname,\n"
            }
         } elsif ($lang eq "en") {
            if ($sex eq "M") {
               $hello = "Dear Mr. $surname,\n"
			         } elsif ($sex eq "F") {
               $hello = "Dear Mrs. $surname,\n"
            } else {
               $hello = "Dear $firstname $surname,\n"
            }
         }

         # Define email subject & body. Language dependant
         if ($lang eq "default" || $lang eq "es-ES") {
            $subject = "Feliz cumpleaños le desea el CELACP!";
            $body = "\n";
            $body .= "El CELACP se complace en hacerle llegar sus mejores deseos en esta fecha tan importante para Ud.\n\n";
            $body .= "Sinceramente esperamos tenga un muy feliz día y esperamos contar con su presencia en nuestro local\n";
            $body .= "o a través de nuestra plataforma virtual en https://biblioteca.celacp.org\n\n";
            $body .= "Atentamente,\n\n";
            $body .= "Centro de Estudios Latinoaméricanos Antonio Cornejo Polar - CELACP -\n";
         } elsif ($lang eq "en") {
            $subject = "Happy birthday from the CELACP!";
            $body = "\n";
            $body .= "The CELACP is pleased to send you its best wishes on this important date for you.\n\n";
            $body .= "We sincerely hope you have a very happy day and we look forward to seeing you at our library\n";
            $body .= "or through our virtual platform at https://biblioteca.celacp.org\n\n";
            $body .= "Sincerely,\n\n";
            $body .= "Center for Latinamerican Estudies Antonio Cornejo Polar - CELACP -\n";
         }
               
         # Final message assembly
         #$to = 'dummy@gmail.com'; # Dummy address for testing purposes. If uncommented, all emails will be sent to this address. Edit it for testing
         $from = 'sourceemail@yourdomain.com';	# This might get overridden by your mail server
         $message = $hello;
         $message .= $body;

         # Create mail "session"
         open(MAIL, "|/usr/sbin/sendmail -t");
 
         # Email Header
         print MAIL "To: $to\n";
         print MAIL "From: $from\n";
         print MAIL "Subject: $subject\n\n";
         
         # Email Body
         print MAIL $message;
         
         # Send email & confirm
         $result = close(MAIL);
         if($result) {  print "A message has been sent to $to\n";} else {  print "Message to $to failed!\n";}
         
         $i++;   
     }  # END IF
  }    # END FOREACH  

 print "$i brthday mensages has been processed\n";
 1;

The associated birthday public report:

SELECT  borrowers.surname, borrowers.firstname, borrowers.email, borrowers.emailpro, borrowers.sex, borrowers.lang 
FROM borrowers 
WHERE  DATE_FORMAT(borrowers.dateofbirth, '%m-%d') = DATE_FORMAT(CURDATE(), '%m-%d')
LIMIT 50

Create a biblios by collection (records $ items) html table to be shown in news

  • Developer: Alvaro Cornejo
  • Module: NA - command line script
  • Purpose: This script, when placed into a cronjob, will regularly update a html file containing a table of bilbio ordered by categories
  • Requirements: a public report, set a cronjob
  • Coding Language: perl
  • Instructions: This script needs a public report that will povide biblio category counts info to script. Report id shall be replaced on the script. The LIMIT 50 on the SQL can be anything. It is just there to override "hardcoded" records limit -10- on public reports. html file generated shall be placed in opac/admin page root folder or other apache accesible folder.
# To run script manually: 

/usr/bin/perl   /PATHTOSCRIPT/SCRIPTFILE.pl

#To use crontab to run script every day at 7am add:

0 7 * * * USER   /usr/bin/perl   /PATHTOSCRIPT/SCRIPTFILE.pl

The script itself:

#!/usr/bin/perl

# Perl library includes
use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request;
use Number::Format 'format_number';
use JSON;

print "\nReport start\n";
my $ua = new LWP::UserAgent;
$ua->agent("Perl API Client/1.0");

# My global variables
my $match = 0;
my $type_opac = "";
my $type_admin = "";
my $collection = "";
my $table_body_opac = "";
my $table_body_admin = "";
my $table_head_admin = "";
my $table_foot_admin = "";
my $table_head_opac = "";
my $table_foot_opac = "";
my $table_head = "";
my $table_end = "";
my $table = "";

# Get report records and place them into an array:
# Fields from report: branchname, homebranch, biblionumber count, items count. Last line has total counts
my $url = "https://OPACDOMAIN/cgi-bin/koha/svc/report?id=XX"; ## UPDATE REPORT ID NUMBER
my $request = HTTP::Request->new("GET" => $url);
my $response = $ua->request($request);
my $json_obj = JSON->new->utf8->decode($response->content);
my $row_num = scalar(@{$json_obj});

# Build tables headers for admin and OPAC page
$table_head .= "<table class=center >\n";

$table_head_admin .= "<tr style='text-align:center'><th colspan=3><b> Registros por Colección </b></th></tr>\n";
$table_head_admin .= "<tr style='font-size: 10.0pt'><th><b> Colección </b></th><th><b> Registros </b></th><th><b> Items </b></th></tr>\n";

$table_head_opac .= "<tr style='text-align:center;color:#D68636'><th colspan=3><b> Registros por Colección </b></th></tr>\n";
$table_head_opac .= "<tr style='font-size: 10.0pt;color:#D68636'><th><b> Colección </b></th><th><b> Registros </b></th><th><b> Items </b></th></tr>\n";

my $i = 1;
# Parse array data and create HTML archive with table
foreach my $row (@{$json_obj}) {
  if ($i < $row_num) {
    #Fix "malformed" accented chars
    my $check_a = chr(225) ;
    my $check_e = chr(233) ;
    my $check_i = chr(237) ;
    my $check_o = chr(243) ;
    my $check_u = chr(250) ;
    my $check_n = chr(241) ;
    @{$row}[0] =~ s/$check_a/á/ig;
    @{$row}[0] =~ s/$check_e/é/ig;
    @{$row}[0] =~ s/$check_i/í/ig;
    @{$row}[0] =~ s/$check_o/ó/ig;
    @{$row}[0] =~ s/$check_u/ú/ig;
    @{$row}[0] =~ s/$check_n/ñ/ig;

    if (@{$row}[1] eq "CELACP") {
      $collection = @{$row}[1];
    } else {
      my @words = split / /, @{$row}[0];
      $collection = $words[-3] . " " . $words[-2] . " " . $words[-1] . " ";
    }
    my $formattedr = format_number(@{$row}[2]);
    my $formattedv = format_number(@{$row}[3]);

    if ($i < $row_num ) {
      # Build table body for admin page
      $type_admin = '<a href="catalogue/search.pl?advsearch=1&limit=branch:' . @{$row}[1] . '"> '. $collection . "</a>";
      $table_body_admin .= "<tr style='font-size: 9.0pt;color:#464646'><td style=text-align:left;> $type_admin </td>";
      $table_body_admin .= "<td style=text-align:right;> $formattedr </td><td style=text-align:right;>  $formattedv  </td></tr>\n";
      # Build table body for opac page
      $type_opac = '<a href="opac-search.pl?advsearch=1&limit=branch:' . @{$row}[1] . '"> '. $collection . "</a>";
      $table_body_opac .= "<tr style='font-size: 9.0pt;color:#464646'><td style=text-align:left;> $type_opac </td>";
      $table_body_opac .= "<td style=text-align:right;> $formattedr </td><td style=text-align:right;>  $formattedv  </td></tr>\n";
    } else {
      # Build body admin
      $type_admin = '<a href="catalogue/search.pl?advsearch=1&limit=branch:' . @{$row}[1] . '"> '. $collection . "</a>";
      $table_body_admin .= "<tr style='font-size: 9.0pt;color:#464646'><td style=text-align:left;> $type_admin </td>";
      $table_body_admin .= "<td style=text-align:right;> $formattedr </td><td style=text-align:right;> $formattedv </td></tr>\n";
      # Build body OPAC
      $type_opac = '<a href="/opac-search.pl?advsearch=1&limit=branch:' . @{$row}[1] . '"> '. $collection . "</a>";
      $table_body_opac .= "<tr style='font-size: 9.0pt;color:#464646'><td style=text-align:left;> $type_opac </td>";
      $table_body_opac .= "<td style=text-align:right;> $formattedr </td><td style=text-align:right;> $formattedv </td></tr>\n";
    }
  } else {
    my $formattedr = format_number(@{$row}[2]);
    my $formattedv = format_number(@{$row}[3]);

    # Build tables footers for admin & opac pages
    $table_foot_admin .= "<tr style='font-size: 10.0pt'><th style=text-align:center;><b> Total: </th>";
    $table_foot_admin .= "<th style=text-align:right;><b> $formattedr </th><th style=text-align:right;><b> $formattedv </b></th></tr>\n";
       
    $table_foot_opac .= "<tr style='font-size: 10.0pt;color:#D68636'><th style=text-align:center;><b> Total: </th>";
    $table_foot_opac .= "<th style=text-align:right;><b> $formattedr </th><th style=text-align:right;><b> $formattedv </b></th></tr>\n";
  }
  $i++;
}
$table_end .= "</table><br>\n";

# Assemble tables for OPAC and admin
my $table_opac = $table_head . $table_head_opac . $table_body_opac . $table_foot_opac . $table_end;
my $table_admin = $table_head . $table_head_admin . $table_body_admin . $table_foot_admin . $table_end;

# Write intranet table file:
my $filename_admin = "/usr/share/koha/intranet/htdocs/report.html";
open(FH, '>', $filename_admin) or warn "Couldn´t write $filename_admin: $!";
print FH $table_admin;
close(FH);
print "File $filename_admin written \n";

# Write OPAC table file:
my $filename_opac = "/usr/share/koha/opac/htdocs/report.html";
open(FH, '>', $filename_opac) or warn "Couldn´t write file $filename_opac: $!";
print FH $table_opac;
close(FH);
print "File $filename_opac written \n";

1;

Associated SQL query:

SELECT 	branchname AS Colección, homebranch AS Code, count( DISTINCT biblionumber) AS Registos, count(itemnumber) AS Items
FROM items 
LEFT JOIN branches ON (items.homebranch=branches.branchcode)
GROUP BY branchname 
WITH ROLLUP LIMIT 50

In order to have html file appear in news, add this code to any news entry. Path shall be relative to opac/admin base url

...
<span >Catalog summary by Collection</span>
<span w3-include-html="../../../report.html"></span>
...

The resulting table:

Collections table.JPG

Koha services/server monitor

  • Developer: Alvaro Cornejo
  • Module: NA - command line script - Koha/server monitor table. Resulting table can be shown anywhere in admin/opac
  • Purpose: This script, when placed into a cronjob, will monitor koha services and get some basic server components stats.
  • Requirements: Set a cronjob, script from nixCraft to monitor server ports (included below), CLI linux commands (uptime, df, memcstat, ps, top, free)
  • Coding Language: perl
  • Instructions: This script run several linux commands to get server data, listening ports and monitor koha specific processes and generates a html file. This file (table) can be placed in admin page news and using ajax can be refreshed as desired. The script can be added to cron job to be updated as required.
#To run script manually: 

/usr/bin/perl   /PATHTOSCRIPT/SCRIPTFILE.pl

#To use crontab, with a 30 second refresh interval, add:

*/1 * * * * USER /usr/bin/perl  /PATHTOSCRIPT/SCRIPTFILE.pl
*/1 * * * * USER sleep 30;  /usr/bin/perl  /PATHTOSCRIPT/SCRIPTFILE.pl

The resulting table:

Monitor table.JPG

Processes counts:

Ap: apache, Ze: zebra, Pl: plack, Me: memcached, Ko: koha, My: mysql, Em: postfix


The script itself

#!/usr/bin/perl
# My includes
use strict;
use warnings;
use Number::Format 'format_number','format_bytes';
use POSIX qw(strftime);

### MY monitorig commands
my $cmd_srv_uptime = "uptime | awk '{print \$3, \$4, \$5}'";
my $cmd_svc_mon = "/PATHTOSCRIPT/runsvcmon.sh";   #ADJUST PATH AS REQUIRED
my $cmd_memca_stats="memcstat --servers=localhost 11211";
my $cmd_apache = "ps -ef |grep apache |wc -l"; # "normal" > 18
my $cmd_memca = "ps -ef |grep memcached |wc -l"; # "normal" = 1
my $cmd_mysql = "ps -ef |grep mysql |wc -l"; # "normal" = 1
my $cmd_mail = "ps -ax |grep postfix |wc -l"; # "normal" = 1
my $cmd_zebra = "ps -ef |grep zebra |wc -l"; # "normal" = 5
my $cmd_plack = "ps -ef |grep plack |wc -l"; # "normal" = 3
my $cmd_koha = "ps -ef |grep koha |wc -l"; # "normal" = 9
my $cmd_disk_use = "df |grep sda | awk '{print \$3, \$5}'"; # 3->bytes 5->%
my $cmd_cpu_use = "top -n 2 -b |grep Cpu |awk '{print \$3}'"; # read 2nd lecture. 1st includes top load
my $cmd_cpu_avg = "top -n 1 -b |grep -i load |awk '{print \$11, \$12, \$13, \$14, \$15, \$16}'"; # gets 3[|||||  ]... split in "[" to extract value
my $cmd_swap_use = "free -b |grep Swap | awk '{print \$2, \$3}'"; # 2->total 3->used 
my $cmd_mem_use = "free -b |grep Mem | awk '{print \$2, \$7}'";  # 2->total 7->available

### OTHER MONITOR COMMANDS THAT MIGHT BE IMPLEMENTED
# my cmd_chk_mysql = "mysqladmin  --user=AMYSQLUSER --password=MYPASSWORD status";
# my cmd_chk_opac = "curl -I "http://biblioteca.celacp.org" 2>&1 | awk '/HTTP\// {print $2}'" # shall return 301(Redirected)
# my cmd_chk_admin = "curl -I "http://biblioteca.celacp.org:8080" 2>&1 | awk '/HTTP\// {print $2}'" #  shall return 200(OK)
# my cmd_chk_ssh = "ssh -q user@server.com  exit | echo $?"; # 0 = OK, else NOK
# curl -I "https://biblioteca.celacp.org" 2>&1 | grep -w "200\|301"
# ping -c 10 www.google.com |grep received |awk '{print $6}'   # % lost pings default 1 ping/second  -i 0.5 two per second
# wc -l /var/log/koka/biblioteca/opac_error  -> check logs number of lines per hour and warn if there is an "unusual" number of extra lines.
# Option: check log size every hour

 #My Global Variables - process counters
 my ($apache_wc, $memca_wc, $mysql_wc, $plack_wc, $koha_wc, $zebra_wc, $disk_use, $cpu_use, $mem_use, $swap_use, $mail_wc );
 # Fonts color variables
 my ($apache_wc_fntc, $memca_wc_fntc, $mysql_wc_fntc, $plack_wc_fntc, $koha_wc_fntc, $zebra_wc_fntc, $disk_use_fntc, $cpu_use_fntc, $mem_use_fntc, $swap_use_fntc, $mail_wc_fntc );
 
 #My Global Variables - arrays
 my (@t, @out, @out_svc, @parts);
 
 # My global variables - Services
 my ($ssh_stat_alt, $ssh_stat_img, $dns_stat_alt, $dns_stat_img, $web_stat_alt, $web_stat_img, $mail_stat_alt, $mail_stat_img);
 my ($kohaadmin_stat_alt, $kohaadmin_stat_img, $kohaopac_stat_alt, $kohaopac_stat_img, $memca_stat_alt, $memca_stat_img, $mysql_stat_alt, $mysql_stat_img);
 my ($stat1, $stat2, $stat3, $stat4, $stat5, $stat6, $stat7, $stat8, $stat9, $stat10, $stat11, $stat12, $stat13, $stat14);
 my ($get_hits, $get_misses, $memc_evict);
 my ($used_mem_icon, $used_mem_icon_alt, $used_swap_icon, $used_swap_icon_alt, $used_disk_icon, $used_disk_icon_alt, $get_hits_icon, $get_hits_icon_alt);
 my ($used_cpu_icon, $used_cpu_icon_alt, $proc_num_icon, $proc_num_icon_alt, $mail_icon, $mail_icon_alt);
 my ($used_swap_percent, $used_mem_percent, $used_disk, $used_cpu_avg, $used_cpu, $cpu_avg, $used_mem, $get_hits_percent);
 
 #My Global Variables - Others
 my ($exit, $i, $t, $s);
 my ($table_body_proc, $table_head_proc, $table_foot_proc, $table_body_use, $table_head_use, $table_foot_use, $table_head, $table_end);
 my ($mail_stat_fntc, $kohaadmin_stat_fntc, $ssh_stat_fntc, $dns_stat_fntc, $web_stat_fntc, $kohaopac_stat_fntc, $mysql_stat_fntc, $memca_stat_fntc);
 
 # Define images for statuses:
 my $ok_img = "/images/ok_23x20_tr.png";
 my $nok_img = "/images/nok_23x20_tr.png";
 my $check_img = "/images/check_23x20_tr.png";
 my $cross_img = "/images/cross_23x20_tr.png";
 my $warn_img = "/images/w_brush_23x20_tr.png";
 my $red_warn_img = "/images/w_red_23x20_tr.png";
 
 # Initialize some variables
 $ssh_stat_img = $dns_stat_img = $mail_stat_img = $web_stat_img = $nok_img;
 $kohaadmin_stat_img = $kohaopac_stat_img = $memca_stat_img = $mysql_stat_img = $nok_img;
 $ssh_stat_alt = $dns_stat_alt = $mail_stat_alt = $web_stat_alt = "Servicio caído";
 $kohaadmin_stat_alt = $kohaopac_stat_alt = $memca_stat_alt = $mysql_stat_alt = "Servicio caído";
 $apache_wc_fntc = $memca_wc_fntc = $mail_wc_fntc= $mysql_wc_fntc = $plack_wc_fntc = $koha_wc_fntc = $zebra_wc_fntc = "";

# Some "cleaning" functions
sub  trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; # remove trailing spaces
sub  trimLF { my $s = shift; $s =~ s/\n//g; return $s }; # remove new lines
sub  trimColon { my $s = shift; $s =~ s/,//g;; return $s }; # remove colon
sub  trimPercent { my $s = shift; $s =~ s/%//g;; return $s }; # remove % sign
 
 ####################################################################
 ### Number of running process
 # Execute each commnad to get the number of running processes:
 # Remove two process from count. Since one is the grep command itself and the other one that perl generates when executing command.
   $apache_wc = `$cmd_apache` -2;
   $zebra_wc = `$cmd_zebra` -2;
   $plack_wc = `$cmd_plack` -2;
   $memca_wc = `$cmd_memca` -2;
   $mail_wc = `$cmd_mail` -2;
   $koha_wc = `$cmd_koha` -2;
   $mysql_wc = `$cmd_mysql` -2;
   
   # If any of the process is not running, error image and red font to the whole process line
   if ($apache_wc == 0 ||  $zebra_wc == 0 || $plack_wc == 0 || $memca_wc == 0 || $koha_wc == 0 || $mysql_wc == 0 || $mail_wc == 0) {
       $proc_num_icon = $nok_img;
       $proc_num_icon_alt = "A monitores process is down";
       $apache_wc_fntc = $memca_wc_fntc = $mysql_wc_fntc = $plack_wc_fntc = $koha_wc_fntc = $zebra_wc_fntc = $disk_use_fntc = $mem_use_fntc = $swap_use_fntc = $mail_wc_fntc = " color=red ";
   } else  {
       # For each process we define "normal" count of running processes. "count" shall be adjusted as "normal usage" counts. If count out of range then warning (icon and orange font)
       $proc_num_icon = $ok_img;
       $proc_num_icon_alt = "Procesos ok";
       if ($apache_wc > 25 || $apache_wc < 5 ) {
          $apache_wc_fntc  = " color=orange ";
          $proc_num_icon = $warn_img;
          $proc_num_icon_alt = "Apache warn";
       }
       if ($zebra_wc > 8 || $zebra_wc < 5 ) {
          $zebra_wc_fntc  = " color=orange ";
          $proc_num_icon = $warn_img;
          $proc_num_icon_alt = "Zebra warn";
       }
       if ($plack_wc > 3 || $plack_wc < 2) {
          $plack_wc_fntc  = " color=orange ";
          $proc_num_icon = $warn_img;
          $proc_num_icon_alt = "Plack warn";
       }
       if ($memca_wc != 1 ) {
          $memca_wc_fntc  = " color=orange ";
          $proc_num_icon = $warn_img;
          $proc_num_icon_alt = "Memca warn";
       }
       if ($mail_wc != 1 ) {
          $mail_wc_fntc = " color=orange ";
          $proc_num_icon = $warn_img;
          $mail_icon_alt = "Mail warn";
       }
       if ($mysql_wc != 1 ) {
          $mysql_wc_fntc = " color=orange ";
          $proc_num_icon = $warn_img;
          $proc_num_icon_alt = "Proc num warn";
       }
       if ($koha_wc > 15 || $koha_wc < 5 ) {
          $koha_wc_fntc = " color=orange ";
          $proc_num_icon = $warn_img;
          $proc_num_icon_alt = "Koha warn";
       }
   }
   # assemble processes line to place in table.
   my $proc_list = "<font $apache_wc_fntc>Ap:$apache_wc</font> <font $zebra_wc_fntc>Ze:$zebra_wc</font> <font $plack_wc_fntc>Pl:$plack_wc</font> <font $memca_wc_fntc>Me:$memca_wc</font> <font $koha_wc_fntc>Ko:$koha_wc</font> <font $mysql_wc_fntc>My:$mysql_wc</font> <font $mail_wc_fntc>Em:$mail_wc</font>";
   print "proc_list: $proc_list\n"; # Just to check from the command line. Can be removed/commented out

  ######################################################################################################
  #### /SDA DISK USAGE GB & %
   $disk_use = `$cmd_disk_use`;
   $exit = `echo $?`;
   if ( $exit != 0 ) {  # command fails for any reason
      print "Falló disk usage\n";
      $used_disk = "No disk info!";
      $used_disk_icon = $red_warn_img;
      $used_disk_icon_alt = "No disk info!";
      $disk_use_fntc  = " id=\"info_red\" ";
   } else {
      @t=split(" ",$disk_use);
      my $used_disk_kb = $t[0] * 1024;
      my $used_disk_percent = trimPercent($t[1]);
      if ($used_disk_percent <= 70) {
          $used_disk_icon = $ok_img;
          $used_disk_icon_alt = "used disk ok";
          $disk_use_fntc  = " id=\"info\" ";
      } else  {
         if ($used_disk_percent > 70 && $used_disk_percent <= 90) {
             $used_disk_icon = $warn_img;
             $used_disk_icon_alt = "used disk warn";
             $disk_use_fntc  = " id=\"info_orange\" ";
         } else {
           if ($used_disk_percent > 90) {
               $used_disk_icon = $red_warn_img;
               $used_disk_icon_alt = "used disk high";
               $disk_use_fntc  = " id=\"info_red\" ";
           }   
         }
      }
      $used_disk_kb = format_bytes($used_disk_kb);
      $used_disk = "$used_disk_kb ($used_disk_percent%)";
   }
   print "used_disk: $used_disk\n";

  ######################################################################################################
  #### CPU USAGE TOTAL AND AVG
  # GET commands results and format values for table
   $cpu_avg = `$cmd_cpu_avg`;
   $exit = `echo $?`;
   if ( $exit != 0 ) {
      print "Falló CPU avg \n";
      $used_cpu_avg = "No CPU info!"
   } else {
      @t=split(" ",$cpu_avg);
      my $rows = scalar(@t);
      $used_cpu_avg = trimColon($t[$rows-3]) . "% " . trimColon($t[$rows-2])."% " . trimColon(trimLF($t[$rows-1]))."%";
   }

   $cpu_use = `$cmd_cpu_use`;
   $exit = `echo $?`;
   if ( $exit != 0 ) {
       print "Falló CPU usage \n";
       $used_cpu = "No CPU info!";
       $used_cpu_icon = $red_warn_img;
       $used_cpu_icon_alt = "No CPU Info!";
       $cpu_use_fntc  = " id=\"info_red\" ";
   } else { 
      @t=split("\n",$cpu_use);
      @t=split('\[',$t[1]);
      my $used_cpu_percent = $t[0];
      if ($used_cpu_percent <= 50) {
          $used_cpu_icon = $ok_img;
          $used_cpu_icon_alt = "used cpu ok";
          $cpu_use_fntc  = " id=\"info\" ";
      } else  {
         if ($used_cpu_percent > 50 && $used_cpu_percent <= 80) {
             $used_cpu_icon = $warn_img;
             $used_cpu_icon_alt = "used cpu warn";
             $cpu_use_fntc  = " id=\"info_orange\" ";
         } else {
            if ($used_cpu_percent > 80) {
                $used_cpu_icon = $red_warn_img;
                $used_cpu_icon_alt = "used cpu high";
                $cpu_use_fntc  = " id=\"info_red\" ";
            }
         }
      }
      $used_cpu = "$used_cpu_percent% ($used_cpu_avg)";
   }
   print "used_cpu: $used_cpu\n";

  ######################################################################################################
  #### MEM USAGE y %
   $mem_use = `$cmd_mem_use`;
   $exit = `echo $?`;
   if ( $exit != 0 ) {
      print "Falló MEM usage \n";
      $used_mem = "No mem info";
      $used_mem_icon = $red_warn_img;
      $used_mem_icon_alt = "No mem info";
      $mem_use_fntc  = " id=\"info_red\" ";
   } else {
      @t=split(" ",$mem_use);
      if ($t[0] == 0) { $used_mem_percent = 0;
      } else { $used_mem_percent = 100-(($t[0]-$t[1])/$t[0]*100); }
   
      $used_mem_percent = format_number($used_mem_percent);
      if ($used_mem_percent <= 75) {
          $used_mem_icon = $ok_img;
          $used_mem_icon_alt = "used mem ok";
          $mem_use_fntc  = " id=\"info\" ";
      } else  {
          if ($used_mem_percent > 75 && $used_mem_percent <= 90) {
             $used_mem_icon = $warn_img;
             $used_mem_icon_alt = "used mem warn";
             $mem_use_fntc  = " id=\"info_orange\" ";
          } else {
             if ($used_mem_percent > 90) {
                $used_mem_icon = $red_warn_img;
                $used_mem_icon_alt = "used mem high";
                $mem_use_fntc  = " id=\"info_red\" ";
             }
          }
      }
      $used_mem = $t[0]-$t[1];
      $used_mem = format_bytes($used_mem) . " ($used_mem_percent%)";
   }
   print "used_mem: $used_mem\n";

   ######################################################################################################
   #### SWAP USAGE y %
   $swap_use = `$cmd_swap_use`;
   $exit = `echo $?`;
   if ( $exit != 0 ) {
      print "Falló SWAP usage \n";
      $used_swap_icon = $red_warn_img;
      $used_swap_icon_alt = "No swap info";
      $swap_use_fntc  = " id=\"info_red\" ";
   }
   @t=split(" ",$swap_use);
   if ($t[0] == 0) { $used_swap_percent = 0;
   } else { $used_swap_percent = $t[1]/$t[0]*100; }
   
   $used_swap_percent = format_number($used_swap_percent);
   if ($used_swap_percent <= 70) {
       $used_swap_icon = $ok_img;
       $used_swap_icon_alt = "used swap ok";
       $swap_use_fntc  = " id=\"info\" ";
   } else  {
      if ($used_swap_percent > 70 && $used_swap_percent <= 90) {
          $used_swap_icon = $warn_img;
          $used_swap_icon_alt = "used swap warn";
          $swap_use_fntc  = " id=\"info_orange\" ";
      } else {
        if ($used_swap_percent > 90) {
            $used_swap_icon = $red_warn_img;
            $used_swap_icon_alt = "used swap high";
            $swap_use_fntc  = " id=\"info_red\" ";
        }
      }
   }
   my $used_swap = $t[1];
   $used_swap = format_bytes($used_swap) . " ($used_swap_percent%)";
   #print "used_swap_percent: $used_swap_percent\n";
   print "used_swap: $used_swap\n";
   print "------------------------\n";
########################################################################################################
# Capturo stats de memcached
@out = `$cmd_memca_stats`;
$exit = `echo $?`;
if ( $exit != 0 )
{
    print "Falló info de memcached\n";
    $get_hits_icon = $nok_img;
    $get_hits_icon_alt = "No memc info";
    $get_hits = "No memc info";
} else {
  for ($i=0; $i<@out; $i++)
  {
    if ($out[$i] =~ /evictions:/) {
       @t=split(" ",$out[$i]);
       $memc_evict = $t[1];
       print "memc_evict: $memc_evict\n";
    }
    if ($out[$i] =~ /get_hits/) {
       @t=split(" ",$out[$i]);
       $get_hits = $t[1];
    }
    if ($out[$i] =~ /get_misses/) {
       @t=split(" ",$out[$i]);
       $get_misses = $t[1];
    }
  } # END FOR
  if ($get_hits == 0 || $get_misses == 0) {
	    $get_hits_percent = 0;
  } else {
     $get_hits_percent = format_number($get_hits / ($get_hits + $get_misses) * 100);
     if ($get_hits_percent > 95) {
         $get_hits_icon = $ok_img;
         $get_hits_icon_alt = "memc gets ok";
     } else  {
         if ($get_hits_percent > 70 && $get_hits_percent <= 95) {
             $get_hits_icon = $warn_img;
             $get_hits_icon_alt = "memc gets warn";
         } else {
            if ($get_hits_percent <= 70) {
                $get_hits_icon = $nok_img;
                $get_hits_icon_alt = "memc gets error";
            }
         }
     }
     $get_hits = format_bytes($get_hits, base => 1000) . "(" . $get_hits_percent . "%)";
  }
}
   print "get_hits: $get_hits\n";

######################################################################################################
# Capturo status de servicios
   @out_svc = `$cmd_svc_mon`;
   $exit = `echo $?`;
   if ( $exit != 0 )
   {
       print "Falló info de memcached\n";
       $get_hits_icon = $nok_img;
       $get_hits_icon_alt = "No memc info";
       $get_hits = "No memc info";
   } else {
     my $svc_num = scalar(@out_svc);
     for ($i=0; $i<@out_svc; $i++) {
         if ($out_svc[$i] =~ /SSH/) {
            @t=split(" ",$out_svc[$i]);
            if ($t[1] =~ /YES/) {
               $ssh_stat_img = $ok_img;
               $ssh_stat_alt = "SSH OK";
               $ssh_stat_fntc = " id=\"svc\" ";
            } else {
               $ssh_stat_img = $cross_img;
               $ssh_stat_alt = "SSH ERROR";
               $ssh_stat_fntc = " id=\"svc_red\" ";
            }
         }
         if ($out_svc[$i] =~ /DNS/) {
            @t=split(" ",$out_svc[$i]);
            if ($t[1] =~ /YES/) {
               $dns_stat_img = $ok_img;
               $dns_stat_alt = "DNS OK";
               $dns_stat_fntc = " id=\"svc\" ";
            } else {
               $dns_stat_img = $cross_img;
               $dns_stat_alt = "DNS ERROR";
               $dns_stat_fntc = " id=\"svc_red\" ";
            }
         }
         if ($out_svc[$i] =~ /WEB/) {
            @t=split(" ",$out_svc[$i]);
            if ($t[1] =~ /YES/) {
               $web_stat_img = $ok_img;
               $web_stat_alt = "WEB OK";
               $web_stat_fntc = " id=\"svc\" ";
            } else {
               $web_stat_img = $cross_img;
               $web_stat_alt = "WEB ERROR";
               $web_stat_fntc = " id=\"svc_red\" ";
            }
         }
         if ($out_svc[$i] =~ /MAIL/) {
            @t=split(" ",$out_svc[$i]);
            if ($t[1] =~ /YES/) {
               $mail_stat_img = $ok_img;
               $mail_stat_alt = "MAIL OK";
               $mail_stat_fntc = " id=\"svc\" ";
            } else {
               $mail_stat_img = $cross_img;
               $mail_stat_alt = "MAIL ERROR";
               $mail_stat_fntc = " id=\"svc_red\" ";
            }
         }
         if ($out_svc[$i] =~ /KOHA-ADMIN/) {
            @t=split(" ",$out_svc[$i]);
            if ($t[1] =~ /YES/) {
               $kohaadmin_stat_img = $ok_img;
               $kohaadmin_stat_alt = "ADMIN OK";
               $kohaadmin_stat_fntc = " id=\"svc\" ";
            } else {
               $kohaadmin_stat_img = $cross_img;
               $kohaadmin_stat_alt = "ADMIN ERROR";
               $kohaadmin_stat_fntc = " id=\"svc_red\" ";
            }
         }
         if ($out_svc[$i] =~ /KOHA-OPAC/) {
            @t=split(" ",$out_svc[$i]);
            if ($t[1] =~ /YES/) {
               $kohaopac_stat_img = $ok_img;
               $kohaopac_stat_alt = "OPAC OK";
               $kohaopac_stat_fntc = " id=\"svc\" ";
            } else {
               $kohaopac_stat_img = $cross_img;
               $kohaopac_stat_alt = "OPAC ERROR";
               $kohaopac_stat_fntc = " id=\"svc_red\" ";
            }
         }
         if ($out_svc[$i] =~ /MEMCACHED/) {
            @t=split(" ",$out_svc[$i]);
            if ($t[1] =~ /YES/) {
               $memca_stat_img = $ok_img;
               $memca_stat_alt = "MEMCACHED OK";
               $memca_stat_fntc = " id=\"svc\" ";
            } else {
               $memca_stat_img = $cross_img;
               $memca_stat_alt = "MEMECACHE ERROR";
               $memca_stat_fntc = " id=\"svc_red\" ";
            }
         }
         if ($out_svc[$i] =~ /MYSQL/) {
            @t=split(" ",$out_svc[$i]);
            if ($t[1] =~ /YES/) {
               $mysql_stat_img = $ok_img;
               $mysql_stat_alt = "MYSQL OK";
               $mysql_stat_fntc = " id=\"svc\" ";
            } else {
               $mysql_stat_img = $cross_img;
               $mysql_stat_alt = "MYSQL ERROR";
               $mysql_stat_fntc = " id=\"svc_red\" ";
            }
         }
     } # END for @out_svc
   } # END else exit
   
######################################################################################################
### NOW assembly the monitor table
my  $table_style = "<style>
td {
   height:20px;
   text-align:center;
   vertical-align:middle;
   font-size:9.0pt;
}
#info {
   height:20px;
   text-align:left;
   vertical-align:middle;
   font-size:9.0pt;
}
#info_red {
   color:red;
   height:20px;
   text-align:left;
   vertical-align:middle;
   font-size:9.0pt;
}
#info_orange {
   color:orange;
   height:20px;
   text-align:left;
   vertical-align:middle;
   font-size:9.0pt;
}
#svc {
   height:20px;
   text-align:center;
   vertical-align:middle;
   font-size:9.0pt;
}
#svc_red {
   color:red;
   height:20px;
   text-align:center;
   vertical-align:middle;
   font-size:9.0pt;
}
</style>";
    $table_head = "";
 my $table_svc_stat_head = "<table>\n";
    $table_svc_stat_head .= "<tr><th><b> Servicio </b></th><th><b> Status </b></th>";
    $table_svc_stat_head .= "<th><b> Servicio </b></th><th><b> Status </b></th></tr>\n";

 my $table_svc_stat_body .= "<tr><td $ssh_stat_fntc > SSH </td>";
    $table_svc_stat_body .= "<td><img alt=\"$ssh_stat_alt\" src=\"$ssh_stat_img\" </td>";
    $table_svc_stat_body .= "<td  $dns_stat_fntc > DNS </td>";
    $table_svc_stat_body .= "<td><img id=\"ssh\" alt=\"$dns_stat_alt\" src=\"$dns_stat_img\" </td></tr>\n";
    $table_svc_stat_body .= "<tr><td $mail_stat_fntc > MAIL </td>";
    $table_svc_stat_body .= "<td><img id=\"mail\"alt=\"$mail_stat_alt\" src=\"$mail_stat_img\" </td>";
    $table_svc_stat_body .= "<td $web_stat_fntc > WEB </td>";
    $table_svc_stat_body .= "<td><img alt=\"$web_stat_alt\" src=\"$web_stat_img\" </td></tr>\n";
    $table_svc_stat_body .= "<tr><td  $kohaadmin_stat_fntc > ADMIN </td>";
    $table_svc_stat_body .= "<td><img alt=\"$kohaadmin_stat_alt\" src=\"$kohaadmin_stat_img\" </td>";
    $table_svc_stat_body .= "<td $kohaopac_stat_fntc > OPAC </td>";
    $table_svc_stat_body .= "<td><img alt=\"$kohaopac_stat_alt\" src=\"$kohaopac_stat_img\" </td></tr>\n";
    $table_svc_stat_body .= "<tr><td $memca_stat_fntc > MEMCA </td>";
    $table_svc_stat_body .= "<td><img alt=\"$memca_stat_alt\" src=\"$memca_stat_img\" </td>";
    $table_svc_stat_body .= "<td $mysql_stat_fntc > MYSQL </td>";
    $table_svc_stat_body .= "<td><img alt=\"$mysql_stat_alt\" src=\"$mysql_stat_img\" </td></tr>\n";

 my $table_other_info = "<tr><th colspan=4><b> Información del servidor </b></th></tr>";
    $table_other_info .= "<tr><td $disk_use_fntc colspan=3 >Disk usage:\t$used_disk</td>";
    $table_other_info .= "<td><img alt=\"$used_disk_icon_alt\" src=\"$used_disk_icon\" </td></tr>\n";
    $table_other_info .= "<tr><td $cpu_use_fntc colspan=3 > CPU usage:<br>$used_cpu</td>";
    $table_other_info .= "<td><img alt=\"$used_cpu_icon_alt\" src=\"$used_cpu_icon\" </td></tr>\n";
    $table_other_info .= "<tr><td $mem_use_fntc colspan=3 >Mem usage:\t$used_mem</td>";
    $table_other_info .= "<td><img alt=\"$used_mem_icon_alt\" src=\"$used_mem_icon\" </td></tr>\n";
    $table_other_info .= "<tr><td $swap_use_fntc colspan=3 >Swap usage:\t$used_swap</td>";
    $table_other_info .= "<td><img alt=\"$used_swap_icon_alt\" src=\"$used_swap_icon\" </td></tr>\n";
    $table_other_info .= "<tr><td id=\"info\" colspan=3 > Memc monit:\tEv:$memc_evict Hits:$get_hits</td>";
    $table_other_info .= "<td><img alt=\"$get_hits_icon_alt\" src=\"$get_hits_icon\" </td></tr>\n";
    $table_other_info .= "<tr><td id=\"info\" colspan=3 > Monitor de procesos:<br>$proc_list </td>";
    $table_other_info .= "<td><img alt=\"$proc_num_icon_alt\" src=\"$proc_num_icon\" </td></tr>\n";

    $table_end .= "</table>\n";

 # -- ASSEMBLE RESULT TABLES
 my $table_svc = $table_style . $table_head . $table_svc_stat_head . $table_svc_stat_body . $table_other_info . $table_end;
######################################################################################################
###  Write the whole to disk
# Write html table to the admin base url
 my $filename_admin = "/usr/share/koha/intranet/htdocs/svc_mon.html";
 open(FH, '>', $filename_admin) or warn "No se puedo guardar el archivo $filename_admin: $!";
 print FH $table_svc;
 close(FH);
######################################################################################################

1;

The nixCraft shell script (runsvcmon.sh) to get listening ports:

#!/bin/bash
# Shell script to monitor running services such as web/http, ssh, mail etc. 
# If service fails it will send an Email to ADMIN user
# -------------------------------------------------------------------------
# Copyright (c) 2006 nixCraft project <http://www.cyberciti.biz/fb/>
# This script is licensed under GNU GPL version 2.0 or above
# -------------------------------------------------------------------------
# This script is part of nixCraft shell script collection (NSSC)
# Visit http://bash.cyberciti.biz/ for more information.
# ----------------------------------------------------------------------
# Last updated: Jun - 15 - 2009.
# Sendmail, root user and syslog functions disabled. Uncomment to enable

# Can ADD any additional port you want to check if its listening
ports="22 53 80 25 8080 443 11211 3306"
 
# service names as per above ports
# Assign to each port a name. In the same order as above.
service="SSH DNS WEB MAIL KOHA-ADMIN KOHA-OPAC MEMCACHED MYSQL"
 
#Email id to send alert
#ADMINEMAIL="admin@myispname.com"
 
#Bin paths, set them according to your Linux distro 
NETSTAT=/bin/netstat
MAIL=/usr/bin/mail
LOGGER=/usr/bin/logger
ID=/usr/bin/id
 
# Red hat usr uncomment 
#MAIL=/bin/mail
#LOGGER=/bin/logger
 
#Counters, set defaults
count=1
status="NO"
#sendmail=0
 
# set the following to 1, if you want message in /var/log/messages via a SYSLOG
logtosyslog=0
 
# Log file used to send an email
LOG="/tmp/services.log.$$"
 
# log message to screen and a log file
log(){
	echo "$@"
	echo "$@" >> $LOG
}
 
# log message and stop script
die(){
	echo "$@"
	exit 999
}
 
# Make sure only root can run it
#is_root(){
#	local id=$($ID -u)
#	[ $id -ne 0 ]  && die "You must be root to run $0."
#}
# Look out for all bins and create a log file
init_script(){
	[ ! -x $MAIL ] && die "$MAIL command not found."
	[ ! -x $NETSTAT ] && die "$NETSTAT command not found."
	[ ! -x $LOGGER ] && die "$LOGGER command not found."
	[ ! -x $ID ] && die "$ID command not found."
#	is_root
	>$LOG
}
  
# check for all running services and shoot an email if service is not running
chk_services(){
	# get open ports 
	REPORTS=$($NETSTAT -tulpn | grep -vE '^Active|Proto' | grep 'LISTEN' | awk '{ print $4}' | awk -F: '{print $NF}' | sed '/^$/d' | sort -u) 

	# okay let us compare them
	for t in $ports
	do
	#	ststus="NO"
		sname=$(echo $service | cut -d' ' -f$count)
		echo -en " $sname: "
		echo -en " $sname: " >> $LOG
		for r in $REPORTS
		do
			if [ "$r" == "$t" ]
			then
				status="YES"	
				#sendmail=1
				break
			fi
		done
			#if [ “$status” == “NO” ]
			#then
			#	sendmail=1
		
			#fi
		echo -n "$status"
		echo ""
		echo -n "$status" >>$LOG
		echo "" >>$LOG
		# Log to a syslog /var/log/messages?
		# This is useful if you have a dedicated syslog server
		[ $logtosyslog -eq 1  ] && $LOGGER "$sname service running : $status"
 
		# Update counters for next round
		count=$( expr $count + 1 )
		status="NO"
	done
	#if [ $sendmail -eq 1 ];
	#then
	#	$MAIL -s "Service Down @ $(hostname)" $ADMINEMAIL < $LOG
	#fi
}
 
### main ###
init_script
chk_services
 
### remove a log file ###
[ -f $LOG ] && /bin/rm -f $LOG

Create an admin news entry and add the following code to have it show in admin news and being refresh as desired

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"> </script>
<div id="koha_mon"><span w3-include-html="../../../svc_mon.html"></span></div>
<script type="text/javascript">
$(document).ready(function () {
setInterval(function(){ $('#koha_mon').load('/svc_mon.html');}, 5000) /* refresh time in milliseconds*/
});
</script>