J’ai acheté récemment un disque réseau DroboFS pour le stockage de données de mon labo. C’est du bon matériel, mais j’ai été très surpris de voir que la seule façon de le configurer est le Drobo Dashboard, un logiciel Windows/Mac qui doit être lancé sur un ordinateur branché au même sous-réseau que le DroboFS (il envoie des paquets broadcast pour localiser le DroboFS, et il est impossible de lui préciser l’adresse IP du DroboFS). Dans mon cas, le DroboFS est branché sur un autre sous-réseau (et dans un autre bâtiment), donc l’autodétection ne fonctionne pas.
Pourquoi n’y a-t-il pas d’interface web, comme sur le Iomega StorCenter Ix2, un disque réseau beaucoup plus bas-de-gamme ? Cela permettrait de l’administrer à distance depuis n’importe quel système !
Un projet récent pour développer un webdashboard existe, mais c’est un projet d’un utilisateur qui à ce jour ne comporte que très peu de code fonctionnel.
En me basant sur ce code, j’ai écrit une page PHP (très mal codée, car je n’ai utilisé PHP qu’une fois ces 10 dernières années) qui affiche les données que j’ai pu récupérer (configuration des dossiers partagés et statut du DroboFS).
edit : captures d’écran anonymisées
Je ne peux toujours pas ajouter des utilisateurs, leur permettre de changer leur mot de passe, ajouter un dossier partager ou changer les droits d’accès, mais c’est un début !
Voici le code source, en espérant qu’il pourra servir à d’autres.
Bug connu : si vous n’avez pas défini d’utilisateur, le script plantera.
Instructions pour l’utiliser : activer les DroboApps sur votre DroboFS (avec Dashboard donc), installer la droboapp Apache, et placer le fichier ci-dessous, nommé droboStatus.php, dans le répertoire /DroboApps/apache/www de votre Drobo. Pour voir le résultat, il suffit de se connecter à http://droboIP:8080/droboStatus.php
<html>
<head>
<title>PHP Test</title>
</head>
<body>
<?php
/*
droboStatus;php : a simple php page to display the status of a DroboFS
Copyright (C) 2011 Manik Bhattacharjee - manik-listes@altern.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/***************************** A short (and incomplete) XML parsing function **********************************************/
function parseBasicXML(&$sourcexml, $markOpen = "DRIShareConfig") {
//echo "<br>Parsing...<br>";
$root = array();
$index = 0;
// Searching for markups
while (preg_match("/([^<]*)<([\s\w\/]+)>/", $sourcexml, $matches, PREG_OFFSET_CAPTURE) ) {
//echo "WHILE ";
$index = $index + 1;
// For each markup
$content = $matches[1][0];
$markup = $matches[2][0];
//echo strlen($sourcexml);
//echo "$markup<br/>";
// Remove this part from the string
$sourcexml = substr($sourcexml, strlen($markup) + 1 + $matches[2][1]);
//echo "Match : $markup\n";
// 3 Cases : <Username>, </Username>, and <Username />
// Entering : call the function recursively, it will return a new node
if (preg_match("/^\s*(\w+)\s*$/", $markup, $mark)){
//echo "New mark ".$mark[1]." <br>\n";
$root[$mark[1].$index] = parseBasicXML($sourcexml, $mark[1]);
}
// Open/Closed markup (e.g. <br /> : add it as a child with no value
elseif (preg_match("/^\s*(\w+)\s*\/$/", $markup, $mark)){
$root[$mark[1].$index] = "novalue";
//echo "OpenClosed mark ".$mark[1]."<br>";
}
// Closed Markup : if it really is our markOpen markup, return ! Otherwise, there is something very wrong !
elseif (preg_match("/^\s*\/\s*(\w+)\s*$/", $markup, $mark)){
if ($mark[1] === $markOpen){
//echo "Closing mark ".$mark[1]." with content $content<br>";
$root["content"] = $content;
return $root;
}else{
echo "Unexpected input $lt;/".$mark[1].", whereas $markOpen was expected.";
}
}else{
echo "Unidentified markup : $markup";
}
//echo "<br>";
}
return $root;
}
/********************************END XML Parsing function********************************************************************/
/***************************** Sub Functions for Drobo configuration Analysis****************************************************************/
function yesOrNo($val){
if($val == 0){ return "No";}else{return "Yes";}
}
function userRights($r){
if ($r == 0){ return "<font color=\"orange\">Read only</font>";}elseif($r == 1){ return "<font color=\"green\">Read/Write</font>";}else{return "Unknown rights !";}
}
function displayPassword($val){ return '***';}
function displayUser($us){
echo "<tr><td><font color = \"blue\"><b>".$us['Username1']['content']."</b></font></td><td>".yesOrNo($us['ValidPassword3']['content'])."</td><td>".yesOrNo($us['EncryptedPassword4']['content'])."</td><td><font color=\"gray\">".displayPassword($us['Password2']['content'])."</font></td></tr>\n";
}
function displayShare($sh){
// echo '<table border="1"><tr><td><b>ShareName<b></td><td>UserName</td><td>Rights</td><td>ShareState</td><td>TimeMachineEnabled</td><td>ShareMaxTMSizeGB</td><td>OldShareName</td></tr>'."\n";
echo "<tr bgcolor=\"#aaaaaa\"><td><font color = \"blue\"><b>".$sh['ShareName1']['content']."</b></font></td><td></td><td></td><td>".$sh['ShareState2']['content']."</td><td>".yesOrNo($sh['TimeMachineEnabled3']['content'])."</td><td>".$sh['ShareMaxTMSizeGB4']['content']."</td><td>".$sh['OldShareName6']."</td></tr>\n";
$su = $sh['ShareUsers5'];
$index = 1;
while($su["ShareUser$index"]){
echo '<tr><td></td><td><font color = "blue">'.$su["ShareUser$index"]['ShareUsername1']['content'].'</font></td><td>'.userRights($su["ShareUser$index"]['ShareUserAccess2']['content']).'</td><td></td><td></td><td></td><td></td></tr>'."\n";
$index = $index+1;
}
//echo "</table>\n";
}
/**************************************End of Sub Functions for Drobo configuration Analysis**************************************************************/
/********************************** Drobo Configuration Analysis and display ******************************************************************/
function displayNASconf($nc){
// USERS
echo "<h2><center>Users</center></h2>\n";
echo '<center><table border="1"><tr><td><b>UserName</b></td><td>Valid Password</td><td>Encrypted Password</td><td width = "50">Password</td></tr>'."\n";
$users = $nc["DRIShareConfig1"]["UserList2"];
$index = 1;
while($users["User$index"]){
displayUser($users["User$index"]);
$index = $index+1;
}
echo "</table></center>\n<br/><br/><br/><br/>\n";
// SHARES
echo "<h2><center>Shares</center></h2>\n";
$shares = $nc["DRIShareConfig1"]['Shares3'];
$index = 1;
echo '<center><table border="1"><tr><td><b>ShareName<b></td><td>UserName</td><td>Rights</td><td>ShareState</td><td>TimeMachineEnabled</td><td>ShareMaxTMSizeGB</td><td>OldShareName</td></tr>'."\n";
while($shares["Share$index"]){
displayShare($shares["Share$index"]);
echo "\n";
$index = $index+1;
}
echo "</table></center>\n<br/><br/><br/><br/>\n";
}
/*********************************** End of Drobo Configuration Analysis and display ************************************************/
/******************************** Sub Functions for Drobo Status Analysis *******************************************************************/
function getCurrentStatus(){
//echo '<form><textarea cols="120" rows="40">';
$cfgServer = "localhost";
$cfgPort = 5000;
$cfgTimeOut = 10;
$drobofs = fsockopen($cfgServer, $cfgPort, $errno, $errstr, $cfgTimeOut);
$statusxml = "";
if (!$drobofs) {
echo "Connexion failed : $errstr\n";
} else {
// read lines until end of "</ESATMUpdate>" is seen
while (!feof($drobofs)) {
$line = fgets($drobofs, 128);
$statusxml = $statusxml.$line;
if (strpos($line, "</ESATMUpdate>") === 0){ break;}
}
}
if ($drobofs) {
fclose($drobofs);
}
return parseBasicXML($statusxml);
}
function simpleString($val){
if ($val === 'novalue'){ return "undefined";}else{return $val['content'];}
}
function simpleMask($val){
return base_convert($val['content'], 10, 2).'b';
}
// From http://code.google.com/p/drobowebdashboard/wiki/ESATMUpdate#ESATMUpdate/mStatus : should analyze the bitmask...
function mStatus($val){
//$ret = $val['content'].' = '.base_convert($val['content'], 10, 2).'b -> ';
$ret = '';
switch ($val['content']){
case 32768:
return $ret.'<font style="BACKGROUND-COLOR: green" color="white">Everything ok, or undergoing relayout</font>';
case 32772:
return $ret.'<font style="BACKGROUND-COLOR: yellow" color="black">Yellow capacity warning, replace first drive</font>';
case 32774:
return $ret.'<font style="BACKGROUND-COLOR: red" color="white">Red capacity warning, replace first drive</font>';
case 32784:
return $ret.'<font style="BACKGROUND-COLOR: red" color="white">Bad drive in 3rd bay</font>';
case 33344:
return $ret.'<font style="BACKGROUND-COLOR: orange" color="black">Rebuilding</font>';
}
$ret = $val['content'].' = '.base_convert($val['content'], 10, 2).'b -> ';
return $ret.'<font color="red">UNKNOWN STATUS - please see http://code.google.com/p/drobowebdashboard/wiki/ESATMUpdate#ESATMUpdate/mStatus</font>';
}
function capacity($val){
// Cannot just divide by 1024*1024*1024 because drobo's php does not support large numbers (above 2 Gb) : just remove the last digits for display
$gb = substr($val['content'],0,-9);
$tb = substr($val['content'],0,-12);
return $gb.' Gb / '.$tb.' Tb.';
// return $val['content'].' bytes / '.$gb.' Gb / '.$tb.' Tb.';
}
function emailConfig($val){
if ($val['content'] == 0){ return "Email alerts <b>not</b> enabled ";} elseif($val['content'] == 1) { return "Email alerts enabled ";} else {return 'Email alert : <b>unknown</b> status';}
}
function mFirmwareFeatureStates($val){
if($val['content'] == 6){ return '<b>Single redundancy</b>'; } elseif($val['content'] == 7){return '<b>Double redundancy</b>';}else{return '<b>Redundancy status unknown !</b>';}
}
function slotmStatus($val){
//$ret = $val['content'].' = '.base_convert($val['content'], 10, 2).'b -> ';
$ret="";
switch ($val['content']){
case 1:
return $ret.'<font color="white" style="BACKGROUND-COLOR: red">Solid red light (add/upgrade drive immediately)</font>';
case 2:
return $ret.'<font color="black" style="BACKGROUND-COLOR: yellow">Solid yellow light (add/upgrade drive soon)</font>';
case 3:
return $ret.'<font color="white" style="BACKGROUND-COLOR: green">Solid green light (everything ok)</font>';
case 4:
return $ret.'<font color="yellow" style="BACKGROUND-COLOR: green">Blinking green/yellow light (relayout)</font>';
case 128:
return $ret.'<font color="gray">No light (empty slot)</font>';
case 134:
return $ret.'<font color="black" style="BACKGROUND-COLOR: red">Blinking red light (defective drive, replace immediately)</font>';
}
return $ret.'<font color="red">UNKNOWN STATUS - please see http://code.google.com/p/drobowebdashboard/wiki/ESATMUpdate#ESATMUpdate/mSlotsExp/n/mStatus</font>';
}
/****************************** End of sub Functions for Drobo Status Analysis *********************************************/
/*********************************** Drobo Status Analysis and display ************************************************/
function displayCurrentStatus($cs, $fullOrNot = 1){
$params = array('mESAUpdateSignature1' => array('?', 'simpleString'),
'mESAUpdateVersion2' => array('?', 'simpleString'),
'mESAUpdateSize3' => array('?', 'simpleString'),
'mESAID4' => array('? - serial number', 'simpleString'),
'mSerial5' => array('Serial number of the device ', 'simpleString'),
'mName6' => array('Name of the device', 'simpleString'),
'mVersion7' => array('Firmware version', 'simpleString'),
'mReleaseDate8' => array('Release date of the firmware ', 'simpleString'),
'mArch9' => array('Hardware architecture', 'simpleString'),
'mFirmwareFeatures10' => array('?', 'simpleString'),
'mFirmwareTestFeatures11' => array('?', 'simpleString'),
'mFirmwareTestState12' => array('?', 'simpleString'),
'mFirmwareTestValue13' => array('?', 'simpleString'),
'mStatus14' => array('Overall status of the device', 'mStatus'),
'mRelayoutCount15' => array('? (Speculation: number of relayouts in the device\'s history)', 'simpleString'),
'mTotalCapacityProtected16' => array('Total capacity', 'capacity'),
'mUsedCapacityProtected17' => array('Used capacity', 'capacity'),
'mFreeCapacityProtected18' => array('Free capacity', 'capacity'),
'mTotalCapacityUnprotected19' => array('?', 'capacity'),
'mUsedCapacityOS20' => array('?', 'capacity'),
'mYellowThreshold21' => array('Threshold of yellow warning about capacity, in 100th of a percent', 'simpleString'),
'mRedThreshold22' => array('Threshold of red warning about capacity, in 100th of a percent', 'simpleString'),
'mUseUnprotectedCapacity23' => array('?', 'capacity'),
'mRealTimeIntegrityChecking24' => array('?', 'simpleString'),
'mStoredFirmwareTestState25' => array('?', 'simpleString'),
'mStoredFirmwareTestValue26' => array('?', 'simpleString'),
'mDiskPackID27' => array('?', 'simpleString'),
'mDroboName28' => array('?', 'simpleString'),
'mConnectionType29' => array('?', 'simpleString'),
'mSlotCountExp30' => array('Maximum number of drive bays (slots)', 'simpleString'),
'mSlotsExp31' => array('Container tag for slots status', 'simpleString'), // SUBFUNCTION
'mLUNUpdates32' => array('Container tag for?', 'simpleString'), //SUBFUNCTION
'mFirmwareFeatureStates33' => array('?', 'mFirmwareFeatureStates'),
'mLUNCount34' => array('?', 'simpleString'),
'mMaxLUNs35' => array('?', 'simpleString'),
'mSledName36' => array('?', 'simpleString'),
'mSledVersion37' => array('?', 'simpleString'),
'mShareCount38' => array('?', 'simpleString'),
'mShareInfo39' => array('?', 'simpleString'),
'mSledStatus40' => array('?', 'simpleString'),
'mSledSerial41' => array('?', 'simpleString'),
'mDiskPackStatus42' => array('?', 'simpleString'),
'DNASStatus43' => array('?', 'simpleString'),
'DNASConfigVersion44' => array('?', 'simpleString'),
'DNASDroboAppsShared45' => array('? (Speculation: indicates whether DroboApps are enabled)', 'simpleString'),
'DNASDiskPackId46' => array('? (Speculation: unique ID for the disk pack) ', 'simpleString'),
'DNASFeatureTable47' => array('?', 'simpleString'),
'DNASEmailConfigEnabled48' => array('Indicates the state of email alerts', 'emailConfig'),
'content' => array('?', 'simpleString')
);
$cs = $cs['ESATMUpdate1'];
//Display slots
$slots = $cs['mSlotsExp31'];
$index = 0;
if ($fullOrNot){
echo "<center><table border = \"1\">\n<tr><td><b>Slot number</b></td><td><b>Status</b></td><td><b>Capacity</b></td><td>Make</td><td>Model</td><td>ESAID</td></tr>\n";
}else{
echo "<center><table border = \"1\">\n<tr><td><b>Slot number</b></td><td><b>Status</b></td><td><b>Capacity</b></td></tr>\n";
}
while($slots['n'.$index.($index+1)]){
$slot = $slots['n'.$index.($index+1)];
if ($fullOrNot){
echo '<tr><td>'.simpleString($slot[mSlotNumber1]).'</td><td>'.slotmStatus($slot[mStatus2]).'</td><td>'.capacity($slot[mPhysicalCapacity6]).'</td><td>'.simpleString($slot[mMake4]).'</td><td>'.simpleString($slot[mModel5]).'</td><td>'.simpleString($slot[mESAID3])."</td></tr>\n";
}else{
echo '<tr><td>'.simpleString($slot[mSlotNumber1]).'</td><td>'.slotmStatus($slot[mStatus2]).'</td><td>'.capacity($slot[mPhysicalCapacity6])."</td></tr>\n";
}
$index = $index+1;
}
echo "</table></center>\n";
if ($fullOrNot){
// Display all parameters
echo "<br><table border = \"1\">\n<tr><td>VarName</td><td>Comment</td><td>Value</td></tr>\n";
foreach ($cs as $key => $val) {
if (!$params[$key][1]){
echo "<br><br><br>UNKNOWN param for $key !<br><br><br>";
}
echo '<tr>';
echo "<td>$key</td><td>".$params[$key][0].'</td><td>'.$params[$key][1]($val).'</td>';
echo "</tr>\n";
}
echo "</table>\n";
}else{ // Just display the comment and value for the most usefull items
echo "<br><center><table border = \"1\">\n";
$keys = array('mName6', 'mStatus14', 'mTotalCapacityProtected16', 'mUsedCapacityProtected17', 'mFreeCapacityProtected18', 'mVersion7', 'DNASEmailConfigEnabled48');
foreach ($keys as $key){
echo '<tr><td>'.$params[$key][0].'</td><td>'.$params[$key][1]($cs[$key])."</td></tr>\n";
}
echo "</table></center>\n";
}
}
/*********************************** End of Drobo Status Analysis and display ************************************************/
/****************************** THE PAGE CONTENT **********************************************************************/
// Parameter : full details or not ?
if($_GET["full"]){ $full = 1;} else {$full = 0;}
if ($full == 0){echo '<h3><a href="droboStatus.php?full=1">Display full details</a></h3>';}else{echo '<h3><a href="droboStatus.php">Display summary</a></h3>';}
// Get the shares configuration
exec("cat /mnt/DroboFS/System/DNAS/configs/shares.conf", $output);
// Make one string from it
$output = implode(" ", $output);
// Parse and display it
$nasconf = parseBasicXML($output);
displayNASconf($nasconf);
// Get the status from port 5000 and display it
echo '<h2><center>Drobo Current Status</center></h2>';
displayCurrentStatus(getCurrentStatus(),$full);
?>
</body>
</html>