Archives par étiquette : php

10 – Module 7 – VPN

J’utilise un petit serveur VPN PPTP pour que mes amis et moi puissions facilement échanger des fichiers ou jouer ensemble en réseau 🙂 C’est très facile à mettre en place aussi bien coté serveur en installant le paquet pptpd, que coté client car c’est nativement supporté par Windows.

Ce module me permet d’afficher la liste des personnes qui sont connectées à mon serveur VPN PPTP.

jarvis_screenshot_VPNJe ne détail pas dans cet article comment installer et configurer PPTPD. Voici un tutoriel qui vous aidera à mettre en place ce type de VPN : https://help.ubuntu.com/community/PPTPServer

Le serveur pptpd étant installé sur ma gateway, il faut procéder de la même manière que pour le module graphique de la bande passante pour transférer les données sur le Pi en utilisant netcat. La seule chose de nouveau ici est la commande last qui permet de récuppérer les informations de connexion des clients du VPN.

Transférer les données vers le Pi

Côté Pi (serveur netcat) :

#!/bin/bash

### BEGIN INIT INFO
# Provides:          nc_vpn_srv
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: vpn for Jarvis
# Description:       Enable service provided by daemon.
### END INIT INFO

dst_port=12346
dst_dir=/home/jarvis/vpn
dst_file=vpn_oberon.log

nc_vpn()
{
  while [ "true" ]
  do
    nc -q 1 -u -l 12346 > $dst_dir/$dst_file < /dev/null
    sleep 5
  done
}

case "$1" in
        stop)
                echo " Stoping vpn for Jarvis..."
                killall vpn
                killall nc
                ;;
        start)
                echo " Starting vpn for Jarvis..."
                nc_vpn&
                exit 0
                ;;
        *)
                exit 1
                ;;
esac

exit 0

Côté gateway (client netcat) :

#! /bin/bash

### BEGIN INIT INFO
# Provides:          nc_vpn_clt
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: vpn for Jarvis
# Description:       Enable service provided by daemon.
### END INIT INFO

src_port=8889
dst_port=12346
dst_host=10.0.0.1

nc_vpn()
{
        while [ "true" ]
        do
                last | grep ppp | grep "logged in" | nc -q 1 -p $src_port -u $dst_host $dst_port
                sleep 5
        done
}

case "$1" in
        stop)
                echo " Stoping vpn for Jarvis..."
                killall vpn
                killall nc
                ;;
        start)
                echo " Starting vpn for Jarvis..."
                nc_vpn&
                exit 0
                ;;
        *)
                exit 1
                ;;
esac

exit 0

Excécutez ces scripts au démarrage du système en les placants dans le répertoire /etc/init.d puis exécutant la commande suivante :

update-rc.d nom_du_script.sh defaults

index.php

Une simple <div> suffit car nous voulons simplement afficher une liste d’utilisateurs :

<div id="vpn"></div>

style.css

Un petit peu de mise en forme pour faire joli 🙂

/* vpn */

div#vpn
{
  left              : 550px;
  bottom            : 10px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
  color : #FFF;
}

td.vpn
{
  text-align        : left;
  font-size         : 30px;
  padding           : 5px;
}

img.vpn
{
  width : 20px;
  height : 20px;
}

javascript.js

Une simple requête AJAX permet de récupérer les données à afficher.

/* VPN */

var vpn_timeout;

function vpn ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=vpn",
    success: function(html){
      $("#vpn").html(html);
    }
  });

  vpn_timeout = setTimeout("vpn()", 5000);
}

ajax.php

Rien de particulier pour ce module :

  /////////////////////////////////////////////////
  //  VPN PPTPD
  /////////////////////////////////////////////////

  if($block == 'vpn'){
    echo vpn();
  }

inc.php

Deux fonctions sont nécessaires pour ce module :

  • vpn_parseData() : parse le fichier envoyé à travers netcat
  • vpn() : créé un tableau HTML avec les noms des utilisateurs connectés
  /////////////////////////////////////////////////
  //  VPN PPTPD
  /////////////////////////////////////////////////

  function vpn () {

    $datas = vpn_parseData ("/home/jarvis/vpn/vpn_oberon.log");

    $html  = '';

    if(sizeof($datas) > 0){
      $html .= '<table cellspacing="0px">';
      foreach($datas as $data){
        $html .= '<tr>';
        $html .= '<td valign="middle"><img class="vpn" src="pict/vpn.png"></td><td class="vpn">'.$data[0].'</td>';
        $html .= '</tr>';
      }
      $html .= '</table>';
    }

    return $html;
  }

  function vpn_parseData ($stat_file) {
    $datas = array();
    if(filemtime($stat_file) < time()-10){return $datas;}
    $stats = fopen($stat_file, 'r');
    while (($line = fgets($stats)) !== false) {
      $explode_line = str_word_count($line, 1, "0123456789.");
      $datas[]  = $explode_line;
    }
    fclose($stats);
    return $datas;
  }

Et voilà pour ce dernier module qui s’avère très simple 🙂

9 – Module 6 – Graphique de la bande passante

Pour diverses raisons, il peut intéressant de s’avoir si notre bande passante internet est saturée ou non. Ce module affiche un historique d’environ 5 minutes de l’utilisation de la bande passante descendante (download) et montante (upload). De cette manière, je peux voir en un seul coup d’oeil si quelque chose d’anormal se passe sur mon réseau.

jarvis_screenshot_ifstat

Introduction

Ce module est de loin le plus compliqué de tous. D’une part, il va falloir récupérer le débit instantané utilisé par l’interface réseau de la gateway qui est reliée à la Box internet. Puis, il va falloir transmettre ces données au Pi sur lequel fonctionne le dashscreen. Enfin, il faudra créer un graphique pour représenter ces informations…

En effet, j’ai choisi de ne pas utiliser de soft type mrtg qui sont spécialisés pour ce genre de chose. Je préfère installer le minimum de soft, et utiliser quelques commandes de base. Pour faire quelque chose de simple (comme afficher la bande passante sur la graphique), pas besoin de sortir l’artillerie lourde 🙂 Et puis …si on ne se compliquait pas la vie, ce serait beaucoup moins drôle, n’est ce pas ? 😉

Mesurer la bande passante

ifstat est un petit outil en ligne de commande qui affiche les statistiques d’utilisation d’une interface réseau. Dans un terminal, taper la commande suivante :

ifstat -n -i eth0

Et vous obtiendrait en continue, quelque chose dans le genre :

       eth0       
 KB/s in  KB/s out
    0.45      0.06
    0.71      0.39
    2.85      0.76
    0.57      0.06
    1.86      0.19
    0.45      0.06
    0.45      0.06
    0.45      0.06
    0.57      0.06
    0.59      0.13
    0.45      0.06
    0.45      0.06
    0.59      0.26
    0.57      0.06
    1.74      0.13
    ...

Chaque seconde on obtient le débit instantané (ou presque), en KBytes/s, c’est à dire en Ko/s 🙂 C’est exactement ce qu’il nous faut 🙂 Il convient maintenant d’envoyer ces informations en continue sur notre Pi.

Transmettre les données au Pi à travers le réseau

Le moyen le plus simple de transmettre une info d’un ordinateur à un autre c’est de les envoyer dans des paquets UDP… C’est tellement trivial qu’il ma fallu plusieurs jours avant d’en avoir l’idée !

La commande netcat est tout à fait appropriée pour ce type d’opération. Sur une machine A qui joue le rôle de serveur faites :

nc -u -l 12345
  • -u : pour utiliser l’UDP
  • -l : pour passer en mode écoute (serveur)
  • 12345 : un numéro de port découte

Puis sur une machine B qui joue le rôle de client, faites :

nc -p 8888 -u 10.0.0.1 12345
  • -p 8888 : un port source
  • -u : pour utiliser l’UDP
  • 10.0.0.1 : adresse IP de la machine A (serveur)
  • 12345 : port d’écoute de la machine A (serveur)

Et hop, tout ce que vous tapez au clavier sur B, s’affiche sur A 🙂

Albert Einstein disait « Tout devrait être rendu aussi simple que possible, mais pas plus ! ».

A méditer 😉

Pour transmettre les données renvoyées par la commande ifstat à travers netcat, nous utiliserons un pipe. Voici les deux scripts complets (client/serveur) pour réaliser cette opération :

  • Script serveur à exécuter sur le Pi nc_ifstat_srv.sh :
### BEGIN INIT INFO 
# Provides: nc_ifstat_srv 
# Required-Start: 
# Required-Stop: 
# Default-Start: 2 3 4 5 
# Default-Stop: 0 1 6 
# Short-Description: ifstat for Jarvis 
# Description: Enable service provided by daemon. 
### END INIT INFO

dst_port=12345
dst_dir=/home/jarvis/ifstat
dst_file=eth0.log

nc -u -l $dst_port >> $dst_dir/$dst_file&
  • Script client à exécuter sur votre gateway/routeur nc_ifstat_clt.sh  :
### BEGIN INIT INFO
# Provides:          nc_ifstat_clt
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: ifstat for Jarvis
# Description:       Enable service provided by daemon.
### END INIT INFO

src_port=8888
dst_port=12345
dst_host=10.0.0.2
eth=eth0

nc_ifstat()
{
	while [ "true" ]
	do
  		ifstat -n -i $eth | nc -p $src_port -u $dst_host $dst_port
  		sleep 5
	done
}

case "$1" in
        stop)
		echo " Stoping ifstat for Jarvis..."
                killall ifstat
		killall nc
		;;
        start)
		echo " Starting ifstat for Jarvis..."
		nc_ifstat&
		exit 0	
                ;;
        *)
                exit 1
                ;;
esac

exit 0

Pour exécuter le script au démarrage de la machine, placer le dans le répertoire /etc/init.d puis lancer la commande :

update-rc.d nom_du_script.sh defaults

A ce stade, vous devriez avoir un fichier sur votre Pi dans lequel, chaque seconde, la bande passante instantanée y est ajoutée en fin de fichier 🙂 Il ne nous reste plus qu’à lire ce fichier avec un script PHP, et dessiner le graphique pour représenter les données qu’il contient 🙂

Création du graphique en PHP

Ici encore, j’aurais pu utiliser une lib graphique en PHP, en JavaScript ou en n’importe quoi d’autre. Mais au lieux de sous exploiter une usine à gaz difficilement personnalisable, j’ai préféré créer ma propre fonction graphique en PHP. Vous allez voir que c’est beaucoup plus simple que ce qu’on peut s’imaginer 🙂

Voici ma fonction imagickHisto() :

function imagickHisto ($max, $eth = '', $up_down = 'down') {

  $datas = parseData ("eth0.log", $up_down);

  $width            = 304; // largeur du graphique
  $height           = 100; // hauteur du graphique
  $padding          = 1;
  $ticks            = 5;
  $background_color = '#000'; // couleur du fond
  $axes_color       = '#555'; // couleur des axes

  if($up_down == 'down'){
    $data_color       = '#1D1'; // couleur du graphique pour le download
  }
  else{
    $data_color       = '#D11'; // couleur du graphique pour l'upload
  }

  $nb_values        = $width - 2*$padding - 2;
  $max_value        = $height - 2*$padding - 4;

  $nb_datas         = sizeof($datas);
  $trim             = $nb_values - $nb_datas;

  if($trim < 0){$trim = 0;}

  $image = new Imagick();
  $image ->newImage( $width, $height, new ImagickPixel($background_color) );
  $draw  = new ImagickDraw();
  $draw->setStrokeColor( new ImagickPixel($axes_color) );

  $xx1    = $padding;
  $xy1    = $height - $padding - 1;
  $xx2    = $width - $padding - 1;
  $xy2    = $xy1;
  $yx1    = $xx1;
  $yy1    = $xy1;
  $yx2    = $yx1;
  $yy2    = $padding;
  $half_y = $height/2;
  $half_x = $width/2;

  $draw->line  ( $xx1, $xy1, $xx2, $xy2 );
  $draw->line  ( $yx1, $yy1, $yx2, $yy2 );

  $draw->line  ( $yx1, $yy2, $yx1+$ticks, $yy2 );
  $draw->line  ( $yx1, $half_y, $yx1+$ticks, $half_y );

  $draw->setStrokeColor( new ImagickPixel($data_color) );

  $first_x = $xx1 + 1 + $trim;
  $last_x  = $xx2 - 1;
  $first_y = $xy1 - 1;
  $last_y  = $yy2 + 1;

  for($i=0;$i<$nb_values;$i++){
    if(isset($datas[$i])){
      $value   = $datas[$i]*$max_value/$max;
      $value_y = $first_y - $value;
      $value_x = $first_x + $i;
      $draw->line  ( $value_x, $first_y, $value_x, $value_y );
    }
  }

  $image->drawImage( $draw );

  $text_draw = new ImagickDraw();
  $text_draw->setFillColor($axes_color);
  $text_draw->setFontSize( 12 );
  $image->annotateImage($text_draw, $half_x-20, $padding+10, 0, "$eth - $up_down");

  $image->setImageFormat( "png" );
  header( "Content-Type: image/png" );
  echo $image;
  exit;
}

Veuillez accepter mes excuses pour le manque cruel de commentaire dont fait l’objet cette fonction. Pour résumer, la fonction prend trois paramètres :

  • $max : la valeur max (en Ko) pour borner le graphique. Par exemple, mon débit réel maximum atteint 1800 Ko/s. Je choisie donc $max = 2000.
  • $eth : nom de l’interface (exemple eth0)
  • $up_down : « up » ou « down » pour spécifier la couleur du graphique ainsi que les données à afficher issue de ifstat

La fonction lit un tableau de donnée renvoyé par la fonction parseData(), puis dessine sur le graphique une barre de 1px de large par donnée. Avec une largeur de 300px, on peut donc afficher 5 minutes d’historique de données 🙂

Voici la fonction parseData() dont le rôle est de lire le fichier contenant les données issues de la commande ifstat :

  function parseData ($stat_file, $up_down) {
    $datas = array();
    if(filemtime($stat_file) < time()-10){return $datas;}
    $stats = fopen($stat_file, 'r');
    while (($line = fgets($stats)) !== false) {
      $explode_line = str_word_count($line, 1, "0123456789.");
      if($up_down == 'down') {
        $datas[]  = $explode_line[0];
      }
      else{
        $datas[]  = $explode_line[1];
      }
    }
    fclose($stats);
    $datas = array_slice($datas, -300);
    return $datas;
  }

Cette fonction prend deux paramètres :

  • $stat_file : fichier à parser
  • $up_down : « up » ou « down » pour distinguer les données d’upload et de download

Pour la suite, nous reprennons la même démarche que pour les autres modules 🙂

index.php

Il faut une div par graphique ; nous en ajoutons donc deux au fichier index.php, une pour l’upload et une pour le download.

 <div id="ifstat_eth0_up"><img id="img_eth0_up" src="pict/blank.png"></div>
 <div id="ifstat_eth0_down"><img id="img_eth0_down" src="pict/blank.png"></div>

Comme pour le module Xplanet, il convient d’initier la source des balises images <img> avec une image vide blank.png.

style.css

Il suffit ici de positionner nos deux <div> :

/* ifstat */

div#ifstat_oberon_up
{
  right             : 20px;
  bottom            : 20px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

div#ifstat_oberon_down
{
  right             : 340px;
  bottom            : 20px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

javascript.js

Une simple requête AJAX permet de récupérer les graphiques à afficher.

/* ifstat */

var ifstat_timeout;

function ifstat () {

  var now             = new Date().getTime();

  var url_down = "ajax.php?block=ifstat&eth=wan&up_down=down&max=2000&hour="+now;
  var img_eth0_down = $("<img />").attr("src", url_eth0_down);
  $("#img_eth0_down").attr("src", url_eth0_down);

  var url_up   = "ajax.php?block=ifstat&eth=wan&up_down=up&max=150&hour="+now;
  var img_eth0_up = $("<img />").attr("src", url_eth0_up);
  $("#img_eth0_up").attr("src", url_eth0_up);

  ifstat_timeout = setTimeout("ifstat()", 5000);
}

Même principe que pour le module Xplanet, il convient de précharger les images avant de les afficher. Je fixe le délais de rafraichissement à 5 secondes, ce qui est suffisant.

ajax.php

Le code à ajouter dans ce fichier reste élémentaire comme pour les autres modules. Il ne faut toutefois pas oublier de prendre en compte les paramètres de la fonction imagickHisto() :

  /////////////////////////////////////////////////
  //  IFSTAT
  /////////////////////////////////////////////////

  if($block == 'ifstat'){
    imagickHisto ($_REQUEST['max'], $_REQUEST['eth'], $_REQUEST['up_down']);
  }


inc.php

Dans ce fichier, il suffit de copier-coller les fonctions imagickHisto() et parseData() qui ont été décrites plus haut dans cet article.

Voilà le rendu final en gros plan, lorsque la bande passante est très sollicitée :

ifstat

7 – Module 4 – Ping de serveur

Si vous avez des serveurs, des NAS, ou n’importe quel autre type de machine dont vous souhaitez connaitre l’état (allumé/éteint, ou disponible/indisponible), ce type de module, encore une fois très simple, vous sera d’une grande utilité 🙂

jarvis_screenshot_pingL’idée est d’afficher en vert les serveurs qui répondent correctement aux solicitations du module, et en rouge ceux qui ne répondent pas. Pour accentuer le caractère alarmant d’une machine qui ne répond pas, nous ajouterons une animation pour optenir ce résultat :

ping

index.php

Comme pour les autres modules, une seule <div> suffit.

 <div id="ping"></div>

La balise reste vide, et sera « remplie » par la fonction JavaScript.

style.css

Pour ce module, la feuille de style est à peine plus longue :

/* ping */

div#ping
{
  left              : 0px;
  bottom            : 0px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

td.ping
{
  font-size         : 30px;
  text-align        : left;
  border-radius     : 10px;
  padding           : 5px 10px;
  font-weight       : bold;
}

td.ping_up
{
  color             : #00DD00;
}

td.ping_down
{
  background-image  : url('pict/warning.gif');
  color             : #DD0000;;
}

Les noms des machines sont affichés dans un tableau <table> dont chaque case <td> peut soit utiliser le style « ping_up« , si la machine répond, soit « ping_down«  si la machine ne répond pas. Dans ce dernier cas, nous utiliserons une image, un gif animé, pour faire « clignoter » la case en rouge, du mois, en donner l’impression 🙂

javascript.js

La fonction javascript de ce module ne fait qu’une requête AJAX, et affiche le résultat dans la div de la page index.php :

/* PING */

var ping_timeout;

function ping ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=ping",
    success: function(html){
      $("#ping").html(html);
    }
  });

  ping_timeout = setTimeout("ping()", 30000);
}

Il est important d’avoir une vision temps réel de l’état de ses machines, nous fixons donc le délai de rafraichissement à 30 secondes. Vous pouvez réduire ce délai selon vos besoins, mais veillez à ce qu’il reste supérieur au temps que met la fonction PHP pour tester toutes vos machines. Dans mon cas, je surveille six machines dont trois à travers internet. En moyenne, il faut 4 à 5 secondes pour tester toutes les machines. Toutefois, il n’est pas rare que cela prenne 20 secondes ! J’ai donc fixer à 30 secondes le temps à attendre entre chaque rafraichissement du module.

ajax.php

Il suffit de rajouter ces quelques lignes au fichier ajax.php pour que la bonne fonction soit appelée :

/////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  if($block == 'ping'){
    echo ping();
  }

inc.php

Dans ce fichier nous allons déclarer la fonction ping() qui est chargée de lancer les requêtes sur les différents serveurs, puis de construire le tableau HTML qui sera affiché.

En général, pour surveiller l’état d’un serveur, on utilise la commande ping qui permet simplement de savoir si la machine ciblée répond aux requêtes ICMP. Cela pose plusieurs problèmes. En effet, il est fort probable que le trafic ICMP soit bloqué par un firewall en amont du serveur visé ce qui faussera le résultat. De plus, si vous souhaiter surveiller un serveur web par exemple, il se peut très bien que la machine réponde bien aux pings, mais que le service web soit indisponible. Les pings n’ont donc que peu d’intéret…

L’idée, est plutôt de verier si les services qui tournent sur nos machines sont bien disponibles. Pour cela, nous allons simplement ouvrir une socket grâce à la fonction PHP fsockopen(). Si l’ouverture de la socket échoue c’est que le service n’est pas disponible 🙂

/////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  function ping () {
    $hosts    = array();
    // Définition des machines à tester
    $hosts_ip = array(
                    'Oberon'    => array('10.0.0.1',  '22'),    // SSH
                    'Triton'    => array('10.0.0.2',  '15915'), // TeamSpeak
                    'Ananke'    => array('10.0.0.3',  '2049'),  // NFS
                    'ds110j'    => array('10.0.0.4',  '2049'),  // NFS
                    'ds210j'    => array('10.0.0.5',  '2049'),  // NFS
                    'ds212j'    => array('10.0.0.6',  '2048')   // NFS
                );

    // pour chaque machine on test l'ouverture d'une socket sur le port spécifié
    // on stock le résultat 'up' ou 'down' dans la variable $hosts
    foreach($hosts_ip as $hostname => $host_data){
      $host_ip    = $host_data[0];
      $host_port  = $host_data[1];
      $socket     = 0;
      $socket     = @fsockopen($host_ip, $host_port, $errno, $errstr, 3);
      if($socket && !$errno){$hosts[$hostname] = 'up';}
      else{$hosts[$hostname] = 'down';}
    }

    // construction du tableau HTML
    // pour chaque machine testée, on affiche son nom et on fixe le style 'ping_up' ou 'ping_down' à la balise td
    $html  = '';
    $html .= '<table cellspacing="10px">';
    $c=0;
    foreach($hosts as $hostname => $host_status){
      if($c == 0){$html .= '<tr>';}
      $html .= '<td class="ping ping_'.$host_status.'">'.$hostname.'</td>';
      $c++;
      if($c == 2){$c = 0; $html .= '</tr>';}
    }
    if($c != 0){$html .= '</tr>';}
    $html .= '</table>';

    return $html;
  }

La variable $hosts_ip contient la liste des machines à tester et afficher. Le nom à affiché, l’adresse IP, et le port TCP du service à tester y sont stocké. Vous devez adapter les noms, adresses IP et numéro de port à vos besoins.

Comme vous pouvez le voir dans les commentaires du code, selon la machine à tester, j’ouvre une socket sur des services tels que SSH, TeamSpeak et NFS.  Cela fonctionne également très bien sur des serveurs FTP, HTTP, POP3, etc,… Tant que le service à tester écoute sur un port TCP, cela fonctionnera 🙂

Note : Il est possible d’optimiser cette fonction PHP en parallèlisant l’ouverture des sockets sur chaque machine. En effet, cette fonction teste les machines une par une ce qui peut être long… En « forkant » cette tâche grâce à la fonction PHP pcntl_fork(), il est possible de lancer toutes les requêtes d’un seul coup et ainsi diminuer le temps total que met la fonction pour renvoyer le tableau HTML à afficher. N’hésitez pas à laisser un commentaire si vous souhaitez plus de détails sur cette manière de procéder 🙂

Optimisation en utilisant le fork

Suite à la demande de lepopeye en commentaire de cet article, je vais expliquer comment paralléliser l’ouverture des sockets et ainsi gagner un temps précieux à chaque exécution de la fonction.

Le principe est simple : Pour chaque machine à tester, nous allons créer un processus fils grâce à la fonction PHP fork_pcntl() qui lancera la fonction fsockopen(). Comme les processus fils et le processus père ne peuvent pas partager de variables (à moins de faire appel à des techniques de mémoire partagée…), il convient de stocker les résultats dans une petite base de données SQlite 🙂 Le processus père attend que tous les processus fils soient terminés pour aller lire le contenu de la base de données SQlite (qui contient l’état de chaque machine). Le reste est inchangé, nous construisons le tableau HTML à afficher comme dans la fonction ping() initiale 🙂 A savoir que le fork n’est possible qu’en ligne de commande, il faudra donc adapter le fichier ajax.php de cette manière :

  /////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  if($block == 'ping'){
    echo shell_exec('php5 fork.php'); // fork.php étant le fichier dans lequel se trouve la fonction ping_fork();
  }

Voici la version « forkée » de la fonction ping(), que vous pouvez utiliser directement en la copiant dans un fichier fork.php :

function ping_fork() {

  $hosts_ip = array(
                    'Oberon'    => array('10.0.0.1',  '22'),    // SSH
                    'Triton'    => array('10.0.0.2',  '15915'), // TeamSpeak
                    'Ananke'    => array('10.0.0.3',  '2049'),  // NFS
                    'ds110j'    => array('10.0.0.4',  '2049'),  // NFS
                    'ds210j'    => array('10.0.0.5',  '2049'),  // NFS
                    'ds212j'    => array('10.0.0.6',  '2048')   // NFS
                );

  $pids  = array();

  // Connexion à la base de données sqlite et création de la table hosts_status si elle n'existe pas encore
  $db = new SQLite3('ifstat/hosts.sqlite');
  $db->exec('CREATE TABLE IF NOT EXISTS hosts_status (host_name VARCHAR(10), host_status VARCHAR(5));');

  // pour chaque machine, on créé un processus fils
  foreach($hosts_ip as $host_name => $host){
    $pids[$host_name] = pcntl_fork();
    if(!$pids[$host_name]) {
      $socket = @fsockopen($host[0], $host[1], $errno, $errstr, 3);
      if($socket && !$errno){$status = 'up';}else{$status = 'down';}
      // on attend que la table hosts_status ne soit plus verrouillée par un éventuel accès concurrent
      if($db->busyTimeout(5000)){
        $db->exec("INSERT INTO hosts_status VALUES ('$host_name', '$status');");
      }
      exit();
    }
  }

  // le processus père doit attendre que tous les processus fils soient terminés
  foreach($pids as $host_name => $pid){
    pcntl_waitpid($pid, $status, WUNTRACED);
  }

  $results = $db->query('select * from hosts_status;');

  $html  = '';
  $html .= '<table cellspacing="10px">';
  $c=0;
  while($host = $results->fetchArray(SQLITE3_ASSOC)){
    if($c == 0){$html .= '<tr>';}
    $html .= '<td class="ping ping_'.$host['host_status'].'">'.$host['host_name'].'</td>';
    $c++;
    if($c == 2){$c = 0; $html .= '</tr>';}
  }
  if($c != 0){$html .= '</tr>';}
  $html .= '</table>';

  $db->exec("DELETE FROM hosts_status;");

  return $html;
}

echo ping_fork();

Le timeout de la fonction fsockopen() est ici fixé à 3 secondes, ce qui veut dire que l’on doit attendre au moins 3 secondes avant de déclarer la machine cible comme « down » (non disponible). Dans la version non forkée de la fonction ping() si une machine ne répondait pas, il fallait attendre 3 secondes avant de tester la machine suivante, et ainsi de suite. Avec mes 6 machines, si aucune ne répond, il aurait fallu attendre 6×3 = 18 secondes avant d’avoir le résultat final. Avec la version forkée, les 6 requêtes sont envoyées en même temps. Quelque soit le nombre de machines à tester, nous aurons donc une réponse au bout de 3 secondes maximum 🙂

J’utilise à présent cette version forkée sur mon dashscreen 🙂 Merci à lepopeye de m’avoir forcé un peu la main 😉

8 – Module 5 – TeamSpeak

Si vous ne connaissez pas TeamSpeak, il s’agit d’un logiciel d’audioconférence qui permet à plusieurs personnes de discuter via Internet (pour plus d’informations http://fr.wikipedia.org/wiki/TeamSpeak). Ce module sert à afficher l’état du serveur TeamSpeak (allumé/éteint) ainsi que la liste des personnes qui y sont connectées.

jarvis_screenshot_TS3Pour obtenir ces informations j’utilise TeamSpeak 3 PHP Framework qui est téléchargeable gratuitement. Cette grosse lib PHP permet d’accéder à toutes les fonctionnalités du serveur TeamSpeak3. Que demande le peuple ? 🙂

Il suffit de télécharger et décompresser TeamSpeak 3 PHP Framework à la racine de votre répertoire web.

index.php

Comme pour les autres modules, une seule <div> suffit. Vous commencez à avoir l’habitude 😉

 <div id="ts3"></div>

La balise reste vide, et sera « remplie » par la fonction JavaScript…

style.css

Feuille de style minimaliste pour ce module :

/* ts3 */

div#ts3
{
  left              : 340px;
  bottom            : 10px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
  color             : #FFF;
  font-weight       : bold;
  text-align        : center;
}

td.ts3_user
{
  text-align        : left;
  font-size         : 30px;
  padding-left      : 10px;
}

javascript.js

Encore une fois, une simple requête AJAX permet de récupérer les informations à afficher.

/* TS3 */

var ts3_timeout;

function ts3 ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=ts3",
    success: function(html){
      $("#ts3").html(html);
    }
  });

  ts3_timeout = setTimeout("ts3()", 10000);
}

Le délai de rafraichissement est fixé à 10 secondes pour voir rapidement qui se connecte/déconnecte du serveur.

ajax.php

Cela se passe d’explications…

  /////////////////////////////////////////////////
  //  TS3
  /////////////////////////////////////////////////

  if($block == 'ts3'){
    echo ts3();
  }

inc.php

C’est ici que nous utilisons TeamSpeak 3 PHP Framework.

Dans un premier temps j’ouvre une socket sur le port 30033 (comme pour le Module 4 – Ping de serveur) pour vérifier que le serveur TeamSpeak tourne bien. Si oui, alors j’affiche la bannière du serveur (l’image triton_ts3.png), puis j’utilise  TeamSpeak 3 PHP Framework pour récupérer la liste et l’état des utilisateurs connectés 🙂

  /////////////////////////////////////////////////
  //  TS3
  /////////////////////////////////////////////////

  function ts3 () {

    $host_ip    = '10.0.0.1';
    $host_port  = '30033';
    $socket     = 0;
    $socket     = @fsockopen($host_ip, $host_port, $errno, $errstr, 3);
    $html       = '';

    if($socket && !$errno){
      require_once("./TS3_PHP_Framework-1.1.12/libraries/TeamSpeak3/TeamSpeak3.php");
      $ts3_ServerInstance = TeamSpeak3::factory("serverquery://login:password@10.0.0.1:10011/");
      $ts3_VirtualServer  = $ts3_ServerInstance->serverGetById(1);

      $html .= '<img width="200px" src="pict/triton_ts3.png" style="margin-bottom : 10px;">';
      $html .= '<table cellspacing="0px" cellpadding="0px">';
      foreach($ts3_VirtualServer->clientList() as $client){
        if($client["client_unique_identifier"] == 'serveradmin'){continue;}
        $html .= '  <tr>';
        $html .= '    <td valign="middle">';
        $html .= '      <img style="width : 20px;" src="./TS3_PHP_Framework-1.1.12/images/viewer/'.$client->getIcon().'.png" alt="">';
        $html .= '    </td>';
        $html .= '    <td valign="middle">';
        $html .= '      '.htmlspecialchars($client);
        $html .= '    </td>';
        $html .= '  </tr>';
      }
      $html .= '</table>';
    }

    return $html;
  }

Ce module est très simple à réaliser grâce à la lib  TeamSpeak 3 PHP Framework 🙂

6 – Module 3 – Météo

Intéressons nous maintenant au module météo qui affiche la météo du jour et les prévisions pour 3 à 5 jours.

jarvis_screenshot_meteoCe module est particulièrement délicat car il est très difficile à personnaliser. En effet, il s’agit d’utiliser des « widgets » mis à disposition par des sites spécialisés et qui proposent des options de configuration limitées. Pour le moment, je n’ai pas encore trouvé de design qui me plaise totalement, je change régulièrement le type d’affichage jusqu’à en trouvé un qui me plaise et qui soit suffisamment lisible de loin.

J’aime beaucoup le design de la météo Google, qui apparait automatiquement quand vous taper « météo » dans la barre de recherche :

meteo_googleCependant, je n’ai pas, pour le moment, trouver le moyen de l’insérer dans une page web…

Widgets

J’ai repéré 3 sites qui proposent des widgets météo plus ou moins complets et plus ou moins jolis : my-meteo.fr, tameteo.com et meteocity.com

Tous propose de personnaliser les informations à afficher, le nombre de jours de prévision, les couleurs des éléments ainsi que différents styles d’icônes.Une fois vos options choisies, le site génère du code HTML à copier/coller dans votre page.

Voici des aperçu des otpions de base de chaque site :

my-meteo.fr

my-meteo

tameteo.com

tameteo

meteocity.com

meteocityEn rédigeant cet article je me suis replongé dans les options du widget proposé par meteocity. Je suis finalement parvenu à trouver quelque chose qui me convient à peu près, même si je ne suis toujours pas emballé à 100% :

meteocity_okC’est déjà un peu plus sympa 🙂 Hormis les informations sur l’humidité et le vent, le reste est parfaitement lisible de loin. Il va falloir ruser pour masquer ces informations inutiles 😉

index.php

Nous ajoutons à notre fichier index.php une simple balise <div> qui reste vide pour le moment.

 <div id="meteo"></div>

Comment pour l’ensemble des modules de ce dashscreen, c’est une fonction JavaScript qui sera charger d’insérer le contenu à afficher dans la div du module concerné.

style.css

Cas particulier de ce module, nous n’avons pas la possibilité d’agir directement sur les styles CSS du code fourni par le site météo. Nous avons devons donc simplement positionner la div qui contiendra les informations météo.

/* meteo */

div#meteo
{
  right             : 0px;
  top               : 300px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
  color             : #FFF;
  font-weight       : bold;
  text-align        : center;
}

javascript.js

La fonction JavaScript de ce module se content d’envoyer une requête AJAX et d’insérer le résultat de la requête dans la div idoine.

/* meteo */

var meteo_timeout;

function meteo ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=meteo",
    success: function(html){
      $("#meteo").html(html);
    }
  });

  meteo_timeout = setTimeout("meteo()", 3600000);
}

Rafraichir les informations météo une fois par heure (toutes les 3600000 ms) est largement suffisant.

inc.php

Pour ce module nous avons besoin d’une fonction PHP qui renvoie le code généré par le widget. Ce qui nous donne une fonction très simple, dans laquelle il vous suffit de copier/coller le code HTML qui vous a généré le site météo :

/////////////////////////////////////////////////
//  Meteo
/////////////////////////////////////////////////

function meteo () {
  $meteo = 'coller ici le code HTML généré par le site météo';
  return $meteo;
}

Masquer les informations inutiles

Si vous souhaitez masquer certaines informations fournis par le widget, il suffit d’ajouter une <div> de couleur noir au dessus des éléments que vous voulez voir disparaitre.

Ajoutez une <div> dans le fichier index.php juste en dessous de la div meteo :

<div id="meteo"></div>
<div id="meteo_black"></div>

Puis modifier le fichier style.css en ajoutant le code suivant dans la section meteo :

div#meteo_black
{
  right             : 0px;
  top               : 420px;
  width             : 600px;
  height            : 100px;
  position          : absolute;
  z-index           : 9999;
  background-color  : rgba(0, 0, 0, 1);
}

Bien entendu, vous devrez adapter la position et la taille de la div en fonction de vos besoins 🙂

Voici le rendu final avec ce nouveau widget météo :

dashscreen_new_meteoJe suis plutôt satisfait du résultat car c’est très lisible et que le design est très épuré 🙂

Avec ces trois premiers modules horloge, xplanet et météo, il est déjà possible de réaliser un beau dashscreen dans votre séjour. Ces informations peuvent être intéressantes pour tout le monde et son utiles au quotidien 🙂

Les prochains modules qui vont suivres sont plus des modules de « monitoring » qui ont pour rôle la surveillance de serveurs et de réseaux.

A suivre…

5 – Module 2 – Xplanet

Voilà mon module préféré 🙂 Celui que je trouve le plus joli et qui mériterait un écran pour lui tout seul. L’affichage de la terre avec la phase d’ensoleillement et la carte des nuages en temps réel ainsi que la phase de la lune 🙂

jarvis_screenshot_xplanetCe module s’appuie sur le logiciel Xplanet qui permet de générer des images de toutes les planètes et satellites natuels du système solaire. Vous pouvez représenter, en temps réel toujours, le système solaire en entier avec la position des astres et planètes, zoomer sur une ou plusieurs planètes etc,…

Concernant la terre, vous pouvez afficher la position des villes et tout un tas d’informations météos notamment. Il est également possible de représenter nos satellites artificiels, avec leur position réelle, leur orbite, etc… Je vous laisse parcourir la galerie de screenshot de Xplanet qui donne une idée des possibilités et du rendu. Perso, je trouve cela magnifique 🙂 Un jour, j’achèterai un grand écran juste pour afficher le système solaire 😀

Xplanet

Installation

Xplanet est un soft qui existe depuis… très longtemps ! Il est dispo dans les dépôts Debian, donc pour l’installer, rien de plus simple :

apt-get install xplanet

Les maps

Xplanet contient de base une « map » pour chaque planète (une image qui représente la surface). Vous pouvez, et je vous le conseille, télécharger des maps de meilleure qualité en haute définition. Il est également intéressant de télécharger une map dédiée à la partie de la surface de la terre non exposée au soleil (nuit), sinon, Xplanet se contentera d’asombrir la map du jour.

Vous trouverez ici de nombreuses maps pour la terre et tous les autres astres du système solaire :

Exemple de map pour la terre – jour :

earth-livingExemple de map pour la terre – nuit :

night-electricXplanet se chargera de créer une combinaison des deux images pour repésenter la partie ensoleillée de la terre et la partie plongée dans l’obscurité.

Génération des images

Xplanet n’est au final qu’un générateur d’image qui représente les astres dans leur état à un instant T et depuis un certain point de vue. Tout se passe en ligne de commande, et comme vous avez pu le constater en parcourant le site web de Xplanet, il y des centaines d’options ! Je vais ici me limiter aux paramètres que j’utilise pour mon affichage.

Je génère en fait deux images distinctes, une pour la terre et une pour la lune. Voici mon fichier de configuration xplanet.conf :

[default]                       # Values in this section apply to all bodies unless overridden below.

arc_color=white
arc_thickness=1
bump_scale=3
cloud_gamma=1
cloud_ssec=false
cloud_threshold=90
color={255,255,255}             # fallback color in case an image map isn't found
draw_orbit=false
grid=false
grid1=6
grid2=15
magnify=1
marker_color=red
max_radius_for_label=3
min_radius_for_label=.01
min_radius_for_markers=40
orbit={-.5,.5,2}
orbit_color={255,255,255}       # color for the orbit
random_origin=true
random_target=true
shade=30                        # 0 = black, 100 = same as dayside
text_color={255,0,0}            # color for text (markers & body label)
twilight=6                      # blend the day and night images for
                                # pixels within this many degrees of
                                # the terminator

[sun]
"Sun"
color={255,255,166}
map=/home/jarvis/xplanet/img/sunmap.jpg
max_radius_for_label=0
shade=100

[earth]
"Earth"
color={28, 82, 110}
map=/home/jarvis/xplanet/img/land_ocean_ice_2048.jpg
night_map=/home/jarvis/xplanet/img/night-dark.jpg
cloud_map=/home/jarvis/xplanet/img/clouds_2048.jpg
min_radius_for_label=0

[moon]
"Moon"
color={100, 100, 100}
map=/home/jarvis/xplanet/img/moonmap2k.jpg

Dans les sections [earth] pour la terre et [moon] pour la lune, vous voyez les chemins vers les maps pour le mode jour « map » et le mode nuit « night_map« .

Les nuages

Vous avez également repéré la map « cloud_map » pour la terre. En effet, Xplanet permet de calquer une image ou carte des nuages sur la terre pour obtenir un résultat très réalise avec la couverture nuageuse modiale presque en temps réel.

cloud_combined_2048Vous pouvez obtenir cette carte des nuages ou « cloud_map » sur différents sites. Selon les sources, elles sont mises à jour entre 1 et 10 fois par jour : http://xplanet.sourceforge.net/clouds.php

Il convient de faire un petit script qui va télécharger la dernière carte des nuages publiée. Une tâche CRON se chargera d’exécuter le script à intervalle régulier. Voici mon script xplanet_cloud.sh :

#!/bin/sh

pat=/home/jarvis/xplanet/img/ # destination de l'image téléchargée
tmp=$pat"tmp_clouds_2048.jpg" # nom du fichier temporaire
img=$pat"clouds_2048.jpg"     # nom du fichier final

rm $tmp # supprimer l'ancien fichier temporaire

wget -O $tmp http://xplanet.sourceforge.net/clouds/clouds_2048.jpg # télécharge l'image

if [ -f $tmp ] ; then # si le fichier a bien été téléchargée...
  mogrify -resize 2000x1000 $tmp # redimenssionne l'image téléchargée pour qu'elle est la même résolution que la map "jour"
  mv $tmp $img # remplace l'ancienne image par la nouvelle
  chown -R jarvis:www-data $pat && chmod -R 775 $pat # change les droits sur le fichier
fi

N’oubliez pas de rendre le script exécutable avec un chmod 755 xplanet_cloud.sh 😉

Et voilà la tâche CRON associée qui peut être configurée en utilisant la commande crontab -e :

0 */4 * * *     /home/jarvis/xplanet/xplanet_cloud.sh

Toutes les 4 heures, le script est exécuté et une nouvelle carte des nuages est téléchargée 🙂

Générer les images

Dernière étape, lancer xplanet au démarrage du système pour qu’il génère les images de la terre et de la lune toutes les N minutes. Pour un petit script qui contient deux commandes (une pour l’image de la terre et une pour l’image de la lune). Voici mon script xplanet.sh :

#!/bin/sh

# TERRE
xplanet -conf /home/jarvis/xplanet/xplanet.conf -output /home/jarvis/xplanet/img/xplanet_earth.png -wait 120 -body earth -latitude 40 -longitude 10 -geometry 500x500 &

# LUNE
xplanet -conf /home/jarvis/xplanet/xplanet.conf -output /home/jarvis/xplanet/img/xplanet_moon.png -wait 600 -body moon -geometry 250x250 &

On passe à la commande xplanet les paramètres suivants :

  • -conf : le fichier de configuration xplanet.conf
  • -output : le fichier de sortie
  • -wait : l’intervalle de temps, en seconde, à attendre entre chaque génération d’image (120 secondes soit 2 minutes pour la terre, pour pouvoir bien observer l’ensoleillement tout au long de la journée. 600 secondes soit 10 minutes sont suffisantes pour la lune, dont la phase varie très lentement).
  • -body : nom du corps céleste à afficher, définie dans le fichier de configuration
  • -geometry : taille, en pixel, de l’image générée
  • -latitude & -longitude : permet de fixer le point centrale de l’image pour la terre. En choisissant la latitude 40 et la longitude 10, l’image sera centrée à peu près sur l’Italie. N’y voyez aucune affinité particulière avec ce pays, simplement c’est un angle de vue qui laisse apparaitre la France et toute l’Europe, l’Afrique en entière, une bonne partie de la Russie, les Amériques, et le pôle nord 🙂 C’est joli ! Vous pouvez également choisir le point de vue du soleil par exemple, pour toujours voir la face ensoleillée de la terre, ce qui permettra de voyager un peu et de ne pas voir toujours la même partie du globe 🙂

Il suffit ensuite de place ce script dans le répertoire /etc/init.d et de lancer la commande suivante pour qu’il soit exécuté à chaque démarrage du système :

update-rc.d xplanet.sh defaults

Nous avons fait la moitié du travail 🙂 Toutes les 2 minutes, une nouvelle image (de 500×500 pixels) de la terre est générée avec une carte des nuages mise à jour toutes les 4 heures. Toutes les 10 minutes, l’image de la phase de la lune ( de 250×250 pixels) est mise à jour.

xplanet_earthxplanet_moon

Il ne nous reste plus qu’à créer le module qui affiche ces images sur notre écran 🙂

index.php

Dans la page d’index, nous ajoutons deux balises <div>, contenant chacune une balise <img>, une pour la terre et une pour la lune.

  <div id="earth"><img id="img_earth" src="pict/blank.png"></div>
  <div id="moon"><img id="img_moon" src="pict/blank.png"></div>

Pour éviter l’affichage d’une erreur au chargement de la page il convient d’initialiser les sources des balises <img> avec une image vide blank.png.

style.css

La feuille de style pour ce module est très simple car il suffit de définir la position et la taille des div qui contiennent les images à afficher.

/* earth */

div#earth
{
  width             : 500px;
  height            : 500px;
  left              : 0px;
  top               : 0px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

/* moon */

div#moon
{
  width             : 250px;
  height            : 250px;
  left              : 500px;
  top               : 0px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

javascript.js

Il s’agit simplement de remplacer l’image affichée par la nouvelle image générée. Le problème c’est que le nom du fichier ne changeant pas, le navigateur aura tendance à afficher l’image qui est déjà stockée dans son cache. Pour palier ce problème nous ajouterons un timestamp (date et heure exprimées en nombre de secondes écoulée de le 1er janver 1970) dans le chemin de l’image. De cette manière le navigateur pensera qu’il s’agit d’une nouvelle image à chaque fois.

Par souci de fluidité, il convient également de précharger les images avant de les substituer à l’écran. On évite ainsi que l’image se charge au fur et à mesure de son affichage.

/* xplanet */

var xplanet_timeout;

function xplanet () {

  var now = new Date().getTime();

  /* préchargement des images */
  var img_earth = $("<img />").attr("src", "xplanet/img/xplanet_earth.png?"+now);
  var img_moon  = $("<img />").attr("src", "xplanet/img/xplanet_moon.png?"+now);

  /* affichage des nouvelles images à l'écran */
  $("#img_earth").attr("src", "xplanet/img/xplanet_earth.png?"+now);
  $("#img_moon").attr("src", "xplanet/img/xplanet_moon.png?"+now);

  xplanet_timeout = setTimeout("xplanet()", 120000);
}

La fonction est exécutée toutes les 120000ms (soit toutes les 2 minutes) ce qui, au final, fait défiler l’ensoleillement de la terre et de la lune au fil de la journée 🙂

C’est vraiment plaisant d’observer le défilement de l’ensoleillement au cours de la journée, et de pouvoir prévoir la météo des prochains jours en constant l’approche d’une grosse perturbation au nord ouest de la France 🙂