Code

Updated opsi
[gosa.git] / gosa-plugins / opsi / admin / opsi / class_opsigeneric.inc
1 <?php
4 /*! \brief  The opsi client base class.
5             This class can be implemented in tow different ways:
6               * as standalone opsi client
7               * as part of the samba tabs 
8             both types will be detected automatically.
10             This class allows to edit the properties of an opsi client
11              and its products.
12  */
13 class opsiGeneric extends plugin
14 {
15   /* Contains a list of all available netboot products 
16    */
17   private $a_availableNetbootProducts = array();
18   private $a_initial_availableNetbootProducts = array();
19   private $s_selectedNetbootProduct = "";  
20   private $s_initial_selectedNetbootProduct = "";  
22   /* Contains a list of all available local products
23    */
24   private $a_availableLocalProducts = array();
25   private $a_selectedLocalProducts = array();
26   private $a_initial_selectedLocalProducts = array();
28   /* Internal veriables 
29    */ 
30   private $opsi;            // The opsi handle
31   public  $parent = NULL;   // The parent object (in case of samba)
33   private $hostId       = ""; // The host Id of the currently edit opsi host  
34   public  $mac          = ""; // The hosts mac address
35   public  $note         = ""; // A note
36   public  $description  = ""; // The client description
38   public  $initial_mac          = ""; 
39   public  $initial_note         = ""; 
40   public  $initial_description  = ""; 
42   private $init_failed = FALSE; // Is true if the opsi communication failed
43   private $standalone  = TRUE;  // Is true if this is a standlone plugin. (Not samba)
44   private $is_installed= FALSE; // Is true is the hast is already installed.
46   public $attributes = array("mac","note","description");
49   /*! \brief  Initialize this class 
50       @param  Object    The GOsa base config.
51       @param  String    The Id of the host that we want to edit.
52       @param  Object    The parent object. (in case of samba)
53    */
54   public function __construct(&$config,$hostId,&$parent = NULL)
55   {
56     /* Create opsi handle
57      */
58     $this->opsi = new opsi($config); 
59     $this->config = $config;
60     
61     /* Check if we are are part of a windows workstation 
62      */
63     $this->parent = $parent;
64     if($parent instanceof wingeneric){
65       $this->standalone  = FALSE;
66     }
68     /* Get hostId 
69      */
70     if($hostId != "new"){
71       if(preg_match("/^opsi:/",$hostId)){
72         $this->hostId = preg_replace("/^opsi:=([^,]*),.*$/","\\1",$hostId);
73       }elseif($this->parent instanceof wingeneric){
74         $this->hostId = $this->parent->cn;
75         $this->hostId = preg_replace('/\$$/',"",$this->hostId);
76       }
77     }
78   
79     /* Try to plugin */
80     $this->init();
81   }
82   
84   /*! \brief  Try to load opsi client informations from the 
85                gosa support daemon.
86    */
87   private function init()
88   {
89     $err = FALSE;
90     $this->init_failed = FALSE;
91     $this->initially_was_account = FALSE; 
93     /* We are a standalone plugin.
94      */
95     if($this->standalone ) {
96       $this->is_account = TRUE;
97     }
99     /* Try to load client infos from the gosa support daemon
100      */
101     if(!empty($this->hostId)){
103       $list = $this->opsi->list_clients($this->hostId);
104       $err |= $this->opsi->is_error();
106       /* Walk through all returned opsi clients and try to detect 
107           one that matches our hostId.
108          #FIXME Implement an opsi method which returns infos for only one opsi client, not all. 
109        */
110       foreach($list as $entry){
111         if(preg_match("/^".normalizePreg($this->hostId)."$/i",$entry['NAME'][0]['VALUE'])){
112           $this->initially_was_account = TRUE; 
113           $this->is_account = TRUE;
114           foreach(array(
115                 "is_installed" => "LASTSEEN",
116                 "description"  => "DESCRIPTION",
117                 "mac"          => "MAC", 
118                 "note"         => "NOTES") as $des => $src){
119             $des2 = "initial_".$des;
120             $this->$des2 = $this->$des = $entry[$src][0]['VALUE'];
121           } 
122           break;
123         }
124       }
125     }
127     /* Read informations about available netboot products. 
128         If not already done, before.
129      */
130     if(!$err && !count($this->a_availableNetbootProducts)){
131       $this->a_availableNetbootProducts = $this->opsi->get_netboot_products();
132       ksort($this->a_availableNetbootProducts);
133       $err |= $this->opsi->is_error();
134     }
136     /* Read informations about available netboot products. 
137         If not already done, before.
138      */
139     if(!$err && !count($this->a_availableLocalProducts)) {
140       $this->a_availableLocalProducts   = $this->opsi->get_local_products();
141       ksort($this->a_availableLocalProducts);
142       $err |= $this->opsi->is_error();
143     }
145     /* Get products selected by this host.
146      */
147     if(!$err && !empty($this->hostId)) {
148       $tmp = array_keys($this->opsi->get_netboot_products($this->hostId));
149       $err |= $this->opsi->is_error();
150       if(count($tmp) && !$err && !isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
151         $this->s_selectedNetbootProduct = $tmp[0];
152       
153         /* Read configuration for "Netboot Products" */
154         if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct])){
155           $CFG = $this->opsi->get_product_properties($this->s_selectedNetbootProduct,$this->hostId);
156           $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'] = $CFG;
157         }
158       }
159       $err |= $this->opsi->is_error();
160     }
162     /* Get all selected local products 
163      */
164     if(!$err && !empty($this->hostId) && !count($this->a_selectedLocalProducts)) {
165       $tmp = $this->opsi->get_local_products($this->hostId); 
166       $err |= $this->opsi->is_error();
167       $this->a_selectedLocalProducts = $tmp;
168     }
170     /* Load product configuration for all already selected products.
171      */
172     if(!$err && !empty($this->hostId)) {
173       foreach($this->a_selectedLocalProducts as $name => $data){
174         if(!$err && !isset($this->a_selectedLocalProducts[$name]['CFG'])){
175           $CFG = $this->opsi->get_product_properties($name,$this->hostId);
176           $err |= $this->opsi->is_error();
177           $this->a_selectedLocalProducts[$name]['CFG'] = $CFG;
178         }
179       }
180     }
181   
182     /* Check if everything went fine else reset everything and display a retry button 
183      */
184     if($err){
185       $this->init_failed = TRUE;
186       
187     }else{
189       /* Remember initial settings */
190       $this->a_initial_selectedLocalProducts = $this->a_selectedLocalProducts;
191       $this->s_initial_selectedNetbootProduct = $this->s_selectedNetbootProduct;
192       $this->a_initial_availableNetbootProducts = $this->a_availableNetbootProducts;
194       /* Ensure that a valid netboot is selected product is.
195        */
196       if(empty($this->s_selectedNetbootProduct)){
197         $this->s_selectedNetbootProduct = key($this->a_availableNetbootProducts);
198       }
199     }
200   }
203   /*! \brief  Check given data.
204       @return Array   Returns an array with all issues.
205    */
206   public function check()
207   {
208     $messages = plugin::check();
210     if(empty($this->hostId)){
211       $messages[] = msgPool::required(_("Name"));
212     }elseif(!preg_match("/\./",$this->hostId)){
214       /* The hostId must contain a domain part 
215        */
216       $messages[] = msgPool::invalid(_("Name"),$this->hostId,"",
217           _("The field 'Name' must contain a domain part!"));
218     }elseif(preg_match("/[^a-z0-9\.\-_]/",$this->hostId)){
219       $messages[] = msgPool::invalid(_("Name"),$this->hostId,"/[a-z0-9\.\-_]/");
220     }
222     /* Ensure that the mac address is valid
223      */
224     if(!tests::is_mac($this->mac) || empty($this->mac)){
225       $messages[] = msgPool::invalid(_("MAC address"),$this->mac,"","00:0C:7F:31:33:F1");
226     }
227     return($messages);
228   }
231   /*! \brief  Create the html ui of this plugin
232       @return String  HTML content.
233    */
234   public function execute()
235   {
236     $display ="";
238     /* The pluign initialization failed due to communication problems with the gosa daemon. 
239        A retry button will be displayed here.
240      */
241     if($this->init_failed){
242       $smarty = get_smarty();
243       $smarty->assign("standalone ", $this->standalone );
244       $smarty->assign("init_failed",TRUE);
245       $smarty->assign("message",$this->opsi->get_error());
246       return($smarty->fetch(get_template_path("generic.tpl",TRUE,dirname(__FILE__))));
247     }  
250     /* If we are not a stand alone opsi client, we must be a samba client 
251        which has the opsi tab enabled.
252        Check if the opsi is added or removed and display state buttons.
253      */
254     if(!$this->standalone ){
256       /* Do we need to flip is_account state? */
257       if(isset($_POST['modify_state'])){
258         if($this->is_account && $this->acl_is_removeable()){
259           $this->is_account= FALSE;
260         }elseif(!$this->is_account && $this->acl_is_createable()){
261           $this->is_account= TRUE;
262         }
263       }
264       if($this->is_account){
265         $display = $this->show_disable_header(msgPool::removeFeaturesButton(_("OPSI")), 
266             msgPool::featuresEnabled(_("OPSI")));
267       }else{
268         $display = $this->show_enable_header(msgPool::addFeaturesButton(_("OPSI")), 
269             msgPool::featuresDisabled(_("OPSI")));
270         return($display);
271       } 
272     } 
274     /* Check if we have a sub dialog opened
275      */
276     if(is_object($this->dialog)){
277       $this->dialog->save_object();
278       return($this->dialog->execute());
279     }
281     /* Create HTML output of this plugin
282      */
283     $smarty = get_smarty();
284     $smarty->assign("standalone", $this->standalone );
285     foreach($this->attributes as $attr){
286       $smarty->assign($attr,$this->$attr);
287     }
289     /* Assign ACLs */    
290     $tmp = $this->plInfo();
291     foreach($tmp['plProvidedAcls'] as $name => $translated){
292       $smarty->assign($name."ACL",$this->getacl($name));
293     }
295     $smarty->assign("is_installed", $this->is_installed);
296     $smarty->assign("init_failed",FALSE);
297     $divSLP = new divSelectBox();
298     $divALP = new divSelectBox();
300     /* Create list of available local products 
301      */
302     foreach($this->a_availableLocalProducts as $name => $data){
303       if(isset($this->a_selectedLocalProducts[$name])) continue;
305       $add_tab  = array("string"   => "<input type='image' src='images/back.png' name='add_lp_".$name."'>");
306       $name_tab = array("string"   => $name);
307       $desc_tab = array("string"   => "<div style='height: 14px;overflow:hidden;'>".$data['DESC']."</div>",
308           "attach"   => "title='".$data['DESC']."' style='border-right:0px;'");
310       if($this->acl_is_writeable("localProduct")){
311         $divALP->AddEntry(array($add_tab,$name_tab,$desc_tab));
312       }else{
313         $divALP->AddEntry(array($name_tab,$desc_tab));
314       }
315     }
317     /* Create list of selected local products 
318      */
319     ksort($this->a_selectedLocalProducts);
320     foreach($this->a_selectedLocalProducts as $name => $data){
322       $name_tab = array("string"   => $name);
323       $desc_tab = array(
324           "string" => "<div style='height: 14px;overflow:hidden;'>".$data['DESC']."</div>",
325           "attach" => "title='".$data['DESC']."'");
327       /* Only display edit button, if there is something to edit 
328        */
329       $edit = "<img src='images/empty.png' alt=' '>";
330       if(count($data['CFG'])){
331         $edit = "<input type='image' src='images/lists/edit.png' name='edit_lp_".$name."'>";
332       }
333       $del  = "<input type='image' src='images/lists/trash.png' name='del_lp_".$name."'>";  
335       $opt_tab  = array("string" => $edit.$del,
336           "attach" => "style='border-right:0px; width: 40px; text-align:right;'");
338       if($this->acl_is_writeable("localProduct")){
339         $divSLP->AddEntry(array($name_tab,$desc_tab,$opt_tab));
340       }else{
341         $divSLP->AddEntry(array($name_tab,$desc_tab));
342       }
343     }
345     /* Check if netboot product is configurable 
346      */
347     $cfg_able =FALSE;
348     if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
349       $cfg_able = count($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG']);
350       $cfg_able &= $this->acl_is_readable("netbootProduct");
351     }
353     $smarty->assign("netboot_configurable",$cfg_able);
354     $smarty->assign("hostId", $this->hostId);
355     $smarty->assign("divSLP", $divSLP->DrawList());
356     $smarty->assign("divALP", $divALP->DrawList());
357     $smarty->assign("SNP", $this->s_selectedNetbootProduct);
358     $smarty->assign("ANP", $this->a_availableNetbootProducts);
359     return($display.$smarty->fetch(get_template_path("generic.tpl",TRUE,dirname(__FILE__))));
360   }
363   /*! \brief  Save modifications using the gosa support daemon.
364    */
365   public function save()
366   {
367     
368     /* Check if we have to create a new opsi client
369         or just have to save client modifications.
370      */
371     if(!$this->initially_was_account && $this->is_account){
372       $res = $this->opsi->add_client($this->hostId,$this->mac,$this->note,$this->description);
373       if($this->opsi->is_error()){
374         msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);    
375         return;
376       }
377     }else{
379       /* Update client modifcations.
380           -Only if necessary  
381        */
382       if($this->note != $this->initial_note || 
383           $this->description != $this->initial_description ||
384           $this->mac != $this->initial_mac){
385         $this->opsi->modify_client($this->hostId,$this->mac,$this->note,$this->description);
386         if($this->opsi->is_error()){
387           msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
388           return;
389         }
390       }
391     }
394     /***********
395       Detect local netboot product changes
396        - Check which products were removed.
397        - Check which products were added. 
398      ***********/
401     /* Detect which products were removed an which added.
402      */
403     $del = array_diff_assoc($this->a_initial_selectedLocalProducts,$this->a_selectedLocalProducts);
404     $add = array_diff_assoc($this->a_selectedLocalProducts,$this->a_initial_selectedLocalProducts);
406     /* Remove products from client
407      */
408     foreach($del as $name => $data){
409       $this->opsi->del_product_from_client($name,$this->hostId);
410       if($this->opsi->is_error()){
411         msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);    
412         return;
413       }
414     }
415     
416     /* Add products to client
417        And set the product properties.
418      */
419     foreach($add as $name => $data){
420       $this->opsi->add_product_to_client($name,$this->hostId);
421       if($this->opsi->is_error()){
422         msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);    
423         return;
424       }
425       if(!empty($data['CFG'])){
426         $this->opsi->set_product_properties($name,$data['CFG'],$this->hostId);
427         if($this->opsi->is_error()){
428           msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);    
429           return;
430         }
431       }
432     }
434     /* Save local product properties 
435      */
436     foreach($this->a_selectedLocalProducts as $name => $data){
437       if(isset($del[$name]) || isset($add[$name])) continue;
439       /* Update product properties if there are changes 
440        */
441       $diffs = $this->get_config_changes($data['CFG'],$this->a_initial_selectedLocalProducts[$name]['CFG']);
442       if(count($diffs)){
443         $this->opsi->set_product_properties($name,$diffs,$this->hostId);
444         if($this->opsi->is_error()){
445           msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);    
446           return;
447         }
448       }
449     }
451     /*********
452       Detect Netboot product changes
453        - Check if another netboot product was selected. 
454        - Check if the product properties were changes.
455      *********/
457     /* Update used netboot product. 
458      */
459     if($this->s_selectedNetbootProduct != $this->s_initial_selectedNetbootProduct){
460       if(!empty($this->s_initial_selectedNetbootProduct)){
461         $this->opsi->del_product_from_client($this->s_initial_selectedNetbootProduct,$this->hostId);
462         if($this->opsi->is_error()){
463           msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);    
464           return;
465         }
466       }
467       $this->opsi->add_product_to_client($this->s_selectedNetbootProduct,$this->hostId);
468       if($this->opsi->is_error()){
469         msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);    
470         return;
471       }
472     }
474     /* Check if we have to update the netboot product properties 
475         This is the case, if this product is newly selected.
476         Or if there was at least one configuration attribute modified.
477      */
478     $cfg_1 = $cfg_2 = array();
479     if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
480       $cfg_1 = $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
481     }
482     if(isset($this->a_initial_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
483       $cfg_2 = $this->a_initial_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
484     }
485     $diffs = $this->get_config_changes($cfg_1,$cfg_2);
486     $to_update = array();
487     if( !$this->initially_was_account || 
488         $this->s_selectedNetbootProduct != $this->s_initial_selectedNetbootProduct){
489       $to_update = $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
490     }elseif(count($diffs)){
491       $to_update = $diffs;
492     }
494     if(count($to_update)){
495       $name = $this->s_selectedNetbootProduct;
496       $this->opsi->set_product_properties($name,$to_update,$this->hostId);
497       if($this->opsi->is_error()){
498         msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
499         return;
500       }
501     }
502   }
504   
505   public function get_config_changes($c1,$c2)
506   {
507     /* Get key which are not present in both entries 
508      */
509     $res = array();
510     foreach($c2 as $name => $value){
511       if(!isset($c1[$name]) || $c1[$name]['DEFAULT'] != $c2[$name]['DEFAULT']){
512         $res[$name] = $c2[$name];
513       }
514     }
515     foreach($c1 as $name => $value){
516       if(!isset($c2[$name]) || $c2[$name]['DEFAULT'] != $c1[$name]['DEFAULT']){
517         $res[$name] = $c1[$name];
518       }
519     }
520     return($res);
521   }
524   /*! \brief  Removes the opsi client 
525    */  
526   public function remove_from_parent()
527   {
528     $this->opsi->del_client($this->hostId);
529     if($this->opsi->is_error()){
530       msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
531       return;
532     }
533   }
536   /*! \brief  Save html posts 
537    */
538   public function save_object()
539   {
540     /* Init failed; reinit is triggered here.
541      */
542     if(isset($_POST['reinit']) && $this->init_failed){
543       $this->init();
544     }
546     /* Property are currently edited, close the dialog. 
547      */
548     if(isset($_POST['cancel_properties']) && is_object($this->dialog)){
549       $this->dialog = NULL;
550     }
551   
552     /* Save product property changes 
553      */
554     if(isset($_POST['save_properties']) && ($this->dialog instanceof opsiProperties)){
555       $this->dialog->save_object();
556       $pro = $this->dialog->get_product();
557       $CFG = $this->dialog->get_cfg();
558       if(isset($this->a_selectedLocalProducts[$pro])){
559         $this->a_selectedLocalProducts[$pro]['CFG'] = $CFG;
560         $this->dialog = NULL;
561       }elseif($this->s_selectedNetbootProduct == $pro){
562         $this->a_availableNetbootProducts[$pro]['CFG'] = $CFG;
563         $this->dialog = NULL;
564       }else{
565         trigger_error("Fatal, unknown product was configured.");
566       }
567     }
569     /* Save html post
570      */
571     if(isset($_POST['opsiGeneric_posted'])){
573       plugin::save_object();
575       /* Get hostId 
576        */
577       if(isset($_POST['hostId']) && $this->standalone && $this->acl_is_writeable("hostId")){
578         $this->hostId = get_post('hostId');
579       }
581       /* Send actions like 'install' or 'wake' to the si server 
582        */
583       if($this->acl_is_writeable("triggerAction") && 
584           isset($_POST['opsi_action']) && 
585           isset($_POST['opsi_trigger_action']) && 
586           $this->standalone ){
587         $action = $_POST['opsi_action'];
588         if($action == "install"){
589           $this->install_client(); 
590         }
591       }
593       /* Get selected netboot product.
594        */
595       if(isset($_POST['opsi_netboot_product']) && $this->acl_is_writeable("netbootProduct")){
596         $SNP = trim($_POST['opsi_netboot_product']);
597         if(isset($this->a_availableNetbootProducts[$SNP])){
598           if(!isset($this->a_availableNetbootProducts[$SNP]['CFG'])){
599             $CFG = $this->opsi->get_product_properties($SNP);
600             $this->a_availableNetbootProducts[$SNP]['CFG'] = $CFG;
601             if($this->opsi->is_error()){
602               $this->init_failed = TRUE;
603               return;
604             }
605           }
606           $this->s_selectedNetbootProduct = $SNP;
607         }
608       }
610       /* Add/remove/edit local products 
611        */
612       foreach($_POST as $name => $value){
614         /* Check if netboot product configuration is requested 
615          */
616         if(preg_match("/^configure_netboot/",$name) && $this->acl_is_readable("netbootProduct")){
617           $pro = $this->s_selectedNetbootProduct;
618           $cfg = $this->a_availableNetbootProducts[$pro]['CFG'];
619           $this->dialog = new opsiProperties($this->config,$pro,$cfg,$this->hostId);
620           break;
621         }
622       
623         /* Add product 
624          */
625         if(preg_match("/^add_lp_/",$name) && $this->acl_is_writeable("localProduct")){
626           $product = preg_replace("/^add_lp_(.*)_.$/","\\1",$name);
627           if(isset($this->a_availableLocalProducts[$product]) && !isset($this->a_selectedLocalProducts[$product])){
628             $this->a_selectedLocalProducts[$product] = $this->a_availableLocalProducts[$product];
629             $CFG = $this->opsi->get_product_properties($product);
630             if($this->opsi->is_error()){
631               $this->init_failed = TRUE;
632               return;
633             }
634             $this->a_selectedLocalProducts[$product]['CFG'] = $CFG;
635           }
636           break;
637         }
638   
639         /* Delete product 
640          */
641         if(preg_match("/^del_lp_/",$name) && $this->acl_is_writeable("localProduct")){
642           $product = preg_replace("/^del_lp_(.*)_.$/","\\1",$name);
643           if(isset($this->a_selectedLocalProducts[$product])){
644             unset($this->a_selectedLocalProducts[$product]);
645           }
646           break;
647         }
648       
649         /* Edit a product  
650          */
651         if(preg_match("/^edit_lp_/",$name) && $this->acl_is_readable("localProduct")){
652           $product = preg_replace("/^edit_lp_(.*)_.$/","\\1",$name);
653           $this->dialog = new opsiProperties($this->config,
654               $product,$this->a_selectedLocalProducts[$product]['CFG'],$this->hostId);
655           break;
656         }
657       }   
658     }
659   }
662   /* Triggers client installation 
663    */
664   function install_client()
665   {
666     $this->opsi->send_action("install",$this->hostId,$this->mac);
667     if($this->opsi->is_error()){
668       msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
669     }
670   }
673   /* Return plugin informations for acl handling */
674   static function plInfo()
675   {
676     return (array(
677           "plShortName"   => _("Generic"),
678           "plDescription" => _("OPSI generic"),
679           "plSelfModify"  => FALSE,
680           "plDepends"     => array(),
681           "plPriority"    => 1,
682           "plSection"     => array("administration"),
683           "plCategory"    => array("opsi" => array("description"  => _("Opsi"),
684               "objectClass"  => "dummy_class_opsi")),
686           "plProvidedAcls"=> array(
687             "hostId"          => _("Name"),
688             "mac"             => _("MAC address"),
689             "description"     => _("Description"),
690             "note"            => _("Note"),
691             "netbootProduct"  => _("Netboot product"),
692             "localProduct"    => _("Local product"),
693             "triggerAction"   => _("Action"))
694           ));
695   }
699 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
700 ?>