]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Add API endpoints to update slots with and without barcodes and use them...
authorMarcin Haba <marcin.haba@bacula.pl>
Sun, 12 May 2019 04:58:29 +0000 (06:58 +0200)
committerMarcin Haba <marcin.haba@bacula.pl>
Sat, 14 Dec 2019 14:50:01 +0000 (15:50 +0100)
- Add protection against HTTP timeout to update slots and label volume commands

20 files changed:
gui/baculum/protected/API/Class/Bconsole.php
gui/baculum/protected/API/Class/VolumeManager.php
gui/baculum/protected/API/Pages/API/SlotsUpdate.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/API/VolumeLabel.php
gui/baculum/protected/API/Pages/API/VolumeLabelBarcodes.php
gui/baculum/protected/API/endpoints.xml
gui/baculum/protected/Common/Class/Errors.php
gui/baculum/protected/Common/Class/Miscellaneous.php
gui/baculum/protected/Web/Lang/en/messages.mo
gui/baculum/protected/Web/Lang/en/messages.po
gui/baculum/protected/Web/Lang/ja/messages.mo
gui/baculum/protected/Web/Lang/ja/messages.po
gui/baculum/protected/Web/Lang/pl/messages.mo
gui/baculum/protected/Web/Lang/pl/messages.po
gui/baculum/protected/Web/Lang/pt/messages.mo
gui/baculum/protected/Web/Lang/pt/messages.po
gui/baculum/protected/Web/Portlets/LabelVolume.php
gui/baculum/protected/Web/Portlets/LabelVolume.tpl
gui/baculum/protected/Web/Portlets/UpdateSlots.php
gui/baculum/protected/Web/Portlets/UpdateSlots.tpl

index be1e04cf8dfac4f3c4522aff5b254f3cbc11edef..eb2ffffed55130802c2b47a1959be5c779360cff 100644 (file)
@@ -33,16 +33,24 @@ class Bconsole extends APIModule {
         */
        const PTYPE_REG_CMD = 0;
        const PTYPE_API_CMD = 1;
-       const PTYPE_CONFIRM_YES_CMD = 2;
+       const PTYPE_BG_CMD = 2;
+       const PTYPE_CONFIRM_YES_CMD = 3;
+       const PTYPE_CONFIRM_YES_BG_CMD = 4;
 
        const BCONSOLE_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <<END_OF_DATA\ngui on\n%s\nquit\nEND_OF_DATA";
 
+       const BCONSOLE_BG_COMMAND_PATTERN = "echo 'gui on\n%s\nquit\n' | nohup %s%s -c %s %s >%s 2>&1 &";
+
        const BCONSOLE_CONFIRM_YES_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <<END_OF_DATA\ngui on\n%s\nyes\nquit\nEND_OF_DATA";
 
+       const BCONSOLE_CONFIRM_YES_BG_COMMAND_PATTERN = "echo 'gui on\n%s\nyes\nquit\n' | nohup %s%s -c %s %s >%s 2>&1 &";
+
        const BCONSOLE_API_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <<END_OF_DATA\ngui on\n.api 2 nosignal api_opts=o\n%s\nquit\nEND_OF_DATA";
 
        const BCONSOLE_DIRECTORS_PATTERN = "%s%s -c %s -l 2>&1";
 
+       const OUTPUT_FILE_PREFIX = 'output_';
+
        private $allowed_commands = array(
                'version',
                'status',
@@ -189,21 +197,9 @@ class Bconsole extends APIModule {
                        $dir = is_null($director) ? '': '-D ' . $director;
                        $sudo = ($this->getUseSudo() === true) ? self::SUDO . ' ' : '';
                        $bconsole_command = implode(' ', $command);
-                       $pattern = null;
-                       switch ($ptype) {
-                               case self::PTYPE_API_CMD: $pattern = self::BCONSOLE_API_COMMAND_PATTERN; break;
-                               case self::PTYPE_CONFIRM_YES_CMD: $pattern = self::BCONSOLE_CONFIRM_YES_COMMAND_PATTERN; break;
-                               default: $pattern = self::BCONSOLE_COMMAND_PATTERN;
-                       }
-                       $cmd = sprintf(
-                               $pattern,
-                               $sudo,
-                               self::getCmdPath(),
-                               self::getCfgPath(),
-                               $dir,
-                               $bconsole_command
-                       );
-                       exec($cmd, $output, $exitcode);
+                       $pattern = $this->getCmdPattern($ptype);
+                       $cmd = $this->getCommand($pattern, $sudo, $dir, $bconsole_command);
+                       exec($cmd['cmd'], $output, $exitcode);
                        if($exitcode != 0) {
                                $emsg = ' Output=>' . implode("\n", $output) . ', Exitcode=>' . $exitcode;
                                throw new BConsoleException(
@@ -211,11 +207,18 @@ class Bconsole extends APIModule {
                                        BconsoleError::ERROR_BCONSOLE_CONNECTION_PROBLEM
                                );
                        } else {
+                               if ($pattern === self::BCONSOLE_BG_COMMAND_PATTERN || $pattern === self::BCONSOLE_CONFIRM_YES_BG_COMMAND_PATTERN) {
+                                       $output = array(
+                                               $bconsole_command,
+                                               json_encode(array('out_id' => $cmd['out_id'])),
+                                               'quit' // in prepareResult() this value is deleted
+                                       );
+                               }
                                $result = $this->prepareResult($output, $exitcode, $bconsole_command);
                        }
                }
                $this->Application->getModule('logging')->log(
-                       $cmd,
+                       $cmd['cmd'],
                        $output,
                        Logging::CATEGORY_EXECUTE,
                        __FILE__,
@@ -225,6 +228,48 @@ class Bconsole extends APIModule {
                return $result;
        }
 
+       private function getCommand($pattern, $sudo, $director, $bconsole_command) {
+               $command = array('cmd' => null, 'out_id' => null);
+               if ($pattern === self::BCONSOLE_BG_COMMAND_PATTERN || $pattern === self::BCONSOLE_CONFIRM_YES_BG_COMMAND_PATTERN) {
+                       $file = $this->prepareOutputFile();
+                       $cmd = sprintf(
+                               $pattern,
+                               $bconsole_command,
+                               $sudo,
+                               self::getCmdPath(),
+                               self::getCfgPath(),
+                               $director,
+                               $file
+                       );
+                       $command['cmd'] = $cmd;
+                       $command['out_id'] = preg_replace('/^[\s\S]+\/output_/', '', $file);
+               } else {
+                       $cmd = sprintf(
+                               $pattern,
+                               $sudo,
+                               self::getCmdPath(),
+                               self::getCfgPath(),
+                               $director,
+                               $bconsole_command
+                       );
+                       $command['cmd'] = $cmd;
+                       $command['out_id'] = '';
+               }
+               return $command;
+       }
+
+       private function getCmdPattern($ptype) {
+               $pattern = null;
+               switch ($ptype) {
+                       case self::PTYPE_API_CMD: $pattern = self::BCONSOLE_API_COMMAND_PATTERN; break;
+                       case self::PTYPE_BG_CMD: $pattern = self::BCONSOLE_BG_COMMAND_PATTERN; break;
+                       case self::PTYPE_CONFIRM_YES_CMD: $pattern = self::BCONSOLE_CONFIRM_YES_COMMAND_PATTERN; break;
+                       case self::PTYPE_CONFIRM_YES_BG_CMD: $pattern = self::BCONSOLE_CONFIRM_YES_BG_COMMAND_PATTERN; break;
+                       default: $pattern = self::BCONSOLE_COMMAND_PATTERN;
+               }
+               return $pattern;
+       }
+
        public function getDirectors() {
                $sudo = ($this->getUseSudo() === true) ? self::SUDO . ' ' : '';
                $cmd = sprintf(
@@ -249,6 +294,30 @@ class Bconsole extends APIModule {
                return in_array($director, $this->getDirectors()->output);
        }
 
+       private function prepareOutputFile() {
+               $dir = Prado::getPathOfNamespace('Application.API.Config');
+               $fname = tempnam($dir, self::OUTPUT_FILE_PREFIX);
+               return $fname;
+       }
+
+       public static function readOutputFile($out_id) {
+               $output = array();
+               $dir = Prado::getPathOfNamespace('Application.API.Config');
+               if (preg_match('/^[a-z0-9]+$/i', $out_id) === 1) {
+                       $file = $dir . '/' . self::OUTPUT_FILE_PREFIX . $out_id;
+                       if (file_exists($file)) {
+                               $output = file($file);
+                       }
+                       $output_count = count($output);
+                       $last = $output_count > 0 ? trim($output[$output_count-1]) : '';
+                       if ($last === 'quit') {
+                               // output is complete, so remove the file
+                               unlink($file);
+                       }
+               }
+               return $output;
+       }
+
        public function testBconsoleCommand(array $command, $cmd_path, $cfg_path, $use_sudo) {
                $this->setEnvironmentParams($cmd_path, $cfg_path, $use_sudo, true);
                $director = '';
index d1ad6e3a2584f5aaa29d19df700f0fefbc960def..edd7d817b0a6268130146811ccd2841d9d516cd9 100644 (file)
@@ -74,7 +74,7 @@ class VolumeManager extends APIModule {
                        foreach($volumes as $volume) {
                                $this->setWhenExpire($volume);
                        }
-               } else {
+               } elseif (is_object($volumes)) {
                        $this->setWhenExpire($volumes);
                }
        }
diff --git a/gui/baculum/protected/API/Pages/API/SlotsUpdate.php b/gui/baculum/protected/API/Pages/API/SlotsUpdate.php
new file mode 100644 (file)
index 0000000..1cebe04
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2019 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.API.Class.Bconsole');
+
+class SlotsUpdate extends BaculumAPIServer {
+
+       public function get() {
+               $output = array();
+               $misc = $this->getModule('misc');
+               if ($this->Request->contains('out_id') && $misc->isValidAlphaNumeric($this->Request->itemAt('out_id'))) {
+                       $out_id = $this->Request->itemAt('out_id');
+                       $output = Bconsole::readOutputFile($out_id);
+               }
+               $this->output = $output;
+               $this->error = VolumeError::ERROR_NO_ERRORS;
+       }
+
+       public function set($id, $params) {
+               $slots = property_exists($params, 'slots') ? $params->slots : 0;
+               $drive = property_exists($params, 'drive') ? intval($params->drive) : 0;
+               $barcodes = ($this->Request->contains('barcodes') && $this->Request->itemAt('barcodes') === 'barcodes');
+               $misc = $this->getModule('misc');
+
+               $storage = null;
+               if (property_exists($params, 'storageid')) {
+                       $storageid = intval($params->storageid);
+                       $result = $this->getModule('storage')->getStorageById($storageid);
+                       if (is_object($result)) {
+                               $storage = $result->name;
+                       }
+               } elseif (property_exists($params, 'storage') && $misc->isValidName($params->storage)) {
+                       $storage = $params->storage;
+               }
+
+               if (!is_null($storage)) {
+                       $result = $this->getModule('bconsole')->bconsoleCommand(
+                               $this->director,
+                               array('.storage')
+                       );
+                       if ($result->exitcode === 0) {
+                               array_shift($result->output);
+                               if (!in_array($storage, $result->output)) {
+                                       $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS;
+                                       $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS;
+                                       return;
+                               }
+                       } else {
+                               $this->output = $result->output;
+                               $this->error = $result->exitcode;
+                               return;
+                       }
+               } else {
+                       $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS;
+                       $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS;
+                       return;
+               }
+
+               if (!$misc->isValidRange($slots)) {
+                       $this->output = VolumeError::MSG_ERROR_INVALID_SLOT;
+                       $this->error = VolumeError::ERROR_INVALID_SLOT;
+                       return;
+               }
+
+               $cmd = array(
+                       'update',
+                       'slots',
+                       'storage="' . $storage . '"',
+                       'drive="' . $drive . '"',
+                       'slot="' . $slots . '"'
+               );
+
+               if ($barcodes === false) {
+                       array_splice($cmd, 2, 0, array('scan'));
+               }
+
+               $result = $this->getModule('bconsole')->bconsoleCommand(
+                       $this->director,
+                       $cmd,
+                       Bconsole::PTYPE_BG_CMD
+               );
+               array_shift($result->output);
+               if ($result->exitcode === 0) {
+                       $this->output = $result->output;
+                       $this->error = VolumeError::ERROR_NO_ERRORS;
+               } else {
+                       $this->output = $result->output;
+                       $this->error = $result->exitcode;
+               }
+       }
+}
+?>
index 7c991ea82822480f2de849af918e9a00ac50875f..d3b65d8b56d03f04370cc05ba060773788bad300 100644 (file)
@@ -24,11 +24,21 @@ Prado::using('Application.API.Class.Bconsole');
 
 class VolumeLabel extends BaculumAPIServer {
 
+       public function get() {
+               $output = array();
+               $misc = $this->getModule('misc');
+               if ($this->Request->contains('out_id') && $misc->isValidAlphaNumeric($this->Request->itemAt('out_id'))) {
+                       $out_id = $this->Request->itemAt('out_id');
+                       $output = Bconsole::readOutputFile($out_id);
+               }
+               $this->output = $output;
+               $this->error = VolumeError::ERROR_NO_ERRORS;
+       }
+
        public function create($params) {
                $volume = property_exists($params, 'volume') ? $params->volume : 0;
                $slot = property_exists($params, 'slot') ? $params->slot : 0;
                $drive = property_exists($params, 'drive') ? intval($params->drive) : 0;
-               $poolid = property_exists($params, 'poolid') ? intval($params->poolid) : 0;
                $misc = $this->getModule('misc');
 
                if (!$misc->isValidName($volume)) {
@@ -111,6 +121,13 @@ class VolumeLabel extends BaculumAPIServer {
                        return;
                }
 
+               $result = $this->getModule('volume')->getVolumeByName($volume);
+               if (is_object($result)) {
+                       $this->output = VolumeError::MSG_ERROR_VOLUME_ALREADY_EXISTS;
+                       $this->error = VolumeError::ERROR_VOLUME_ALREADY_EXISTS;
+                       return;
+               }
+
                $cmd = array(
                        'label',
                        'volume="' . $volume . '"',
@@ -121,8 +138,10 @@ class VolumeLabel extends BaculumAPIServer {
                );
                $result = $this->getModule('bconsole')->bconsoleCommand(
                        $this->director,
-                       $cmd
+                       $cmd,
+                       Bconsole::PTYPE_BG_CMD
                );
+               array_shift($result->output);
                if ($result->exitcode === 0) {
                        $this->output = $result->output;
                        $this->error = VolumeError::ERROR_NO_ERRORS;
index f7124f590cbef5e42357b5369d58041d1df1958f..4f9518477e20a5f5073b73873b2f1b41f9d913c5 100644 (file)
@@ -24,10 +24,20 @@ Prado::using('Application.API.Class.Bconsole');
 
 class VolumeLabelBarcodes extends BaculumAPIServer {
 
+       public function get() {
+               $output = array();
+               $misc = $this->getModule('misc');
+               if ($this->Request->contains('out_id') && $misc->isValidAlphaNumeric($this->Request->itemAt('out_id'))) {
+                       $out_id = $this->Request->itemAt('out_id');
+                       $output = Bconsole::readOutputFile($out_id);
+               }
+               $this->output = $output;
+               $this->error = VolumeError::ERROR_NO_ERRORS;
+       }
+
        public function create($params) {
                $slots = property_exists($params, 'slots') ? $params->slots : 0;
                $drive = property_exists($params, 'drive') ? intval($params->drive) : 0;
-               $poolid = property_exists($params, 'poolid') ? intval($params->poolid) : 0;
                $misc = $this->getModule('misc');
 
                $storage = null;
@@ -41,6 +51,12 @@ class VolumeLabelBarcodes extends BaculumAPIServer {
                        $storage = $params->storage;
                }
 
+               if (!$misc->isValidRange($slots)) {
+                       $this->output = VolumeError::MSG_ERROR_INVALID_SLOT;
+                       $this->error = VolumeError::ERROR_INVALID_SLOT;
+                       return;
+               }
+
                $pool = null;
                if (property_exists($params, 'poolid')) {
                        $poolid = intval($params->poolid);
@@ -109,8 +125,9 @@ class VolumeLabelBarcodes extends BaculumAPIServer {
                $result = $this->getModule('bconsole')->bconsoleCommand(
                        $this->director,
                        $cmd,
-                       Bconsole::PTYPE_CONFIRM_YES_CMD
+                       Bconsole::PTYPE_CONFIRM_YES_BG_CMD
                );
+               array_shift($result->output);
                if ($result->exitcode === 0) {
                        $this->output = $result->output;
                        $this->error = VolumeError::ERROR_NO_ERRORS;
index 31ea654edf339f064570571a8ac2967e96de47a2..e934106c63f4fea3bbb34d2be0e321eea08974d4 100644 (file)
@@ -44,6 +44,8 @@
        <url ServiceParameter="API.JobsOnVolume" pattern="api/v1/volumes/{id}/jobs/" parameters.id="\d+" />
        <url ServiceParameter="API.VolumeLabel" pattern="api/v1/volumes/label/" />
        <url ServiceParameter="API.VolumeLabelBarcodes" pattern="api/v1/volumes/label/barcodes/" />
+       <url ServiceParameter="API.SlotsUpdate" pattern="api/v1/volumes/update/" />
+       <url ServiceParameter="API.SlotsUpdate" pattern="api/v1/volumes/update/{barcodes}/" parameters.barcodes="barcodes" />
        <!-- pools endpoints -->
        <url ServiceParameter="API.Pools" pattern="api/v1/pools/" />
        <url ServiceParameter="API.Pool" pattern="api/v1/pools/{id}/" parameters.id="\d+" />
index 87c3c32f8b2c17c9e2f3140201720bdf90b5c0b6..53d2f4ee23644c15907c3db124eeb99bbc07226d 100644 (file)
@@ -76,10 +76,12 @@ class VolumeError extends GenericError {
        const ERROR_VOLUME_DOES_NOT_EXISTS = 30;
        const ERROR_INVALID_VOLUME = 31;
        const ERROR_INVALID_SLOT = 32;
+       const ERROR_VOLUME_ALREADY_EXISTS = 33;
 
        const MSG_ERROR_VOLUME_DOES_NOT_EXISTS = 'Volume does not exist.';
        const MSG_ERROR_INVALID_VOLUME = 'Invalid volume.';
        const MSG_ERROR_INVALID_SLOT = 'Invalid slot.';
+       const MSG_ERROR_VOLUME_ALREADY_EXISTS = 'Volume already exists.';
 }
 
 class PoolError extends GenericError {
index ad3d65cbcec96f9a639edeaf586a868c1c836d77..a8a0a6c033e74dd7ca519f38381b65b51f27870e 100644 (file)
@@ -243,6 +243,10 @@ class Miscellaneous extends TModule {
                return (preg_match('/^[\d\-\,]+$/', $range) === 1);
        }
 
+       public function isValidAlphaNumeric($str) {
+               return (preg_match('/^[a-zA-Z0-9]+$/', $str) === 1);
+       }
+
 
        /**
         * Writing INI-style configuration file.
index c4080111522ddf0d127186ddd4386b0071547374..643934bf5d70920bc3c23e477583594921e35e20 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ
index 0bf5a0ba30ca1f1b4cf14dc81b73f5104e29bb0e..e9853b30ebf54c1f96d060c3ee42200e30b22f4d 100644 (file)
@@ -602,12 +602,6 @@ msgstr "Label"
 msgid "Use barcodes as label:"
 msgstr "Use barcodes as label:"
 
-msgid "Slots for label:"
-msgstr "Slots for label:"
-
-msgid "Slots for update:"
-msgstr "Slots for update:"
-
 msgid "Update barcodes slots"
 msgstr "Update barcodes slots"
 
@@ -2029,3 +2023,27 @@ msgstr "Input must be between 1 and 1000."
 
 msgid "Schedule status"
 msgstr "Schedule status"
+
+msgid "Slots to label (ex. 4 or 1-5 or 2,4,6-10):"
+msgstr "Slots to label (ex. 4 or 1-5 or 2,4,6-10):"
+
+msgid "Labeling status:"
+msgstr "Labeling status:"
+
+msgid "Ready to label"
+msgstr "Ready to label"
+
+msgid "Finished"
+msgstr "Finished"
+
+msgid "Updating status:"
+msgstr "Updating status:"
+
+msgid "Ready to update"
+msgstr "Ready to update"
+
+msgid "Updating..."
+msgstr "Updating..."
+
+msgid "Slots to update (ex. 4 or 1-5 or 2,4,6-10):"
+msgstr "Slots to update (ex. 4 or 1-5 or 2,4,6-10):"
index b71ad3bde945495b73cc0ce3203641da7e6b3427..429200c40732f81cb17448504feba53ee3a769a6 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ
index e9765e27a7da5a0cecc2723ed9fbc9f2cd9c4a7e..09b1d6d60d0aaab3a9620cf2faf98eb2af3ed4a7 100644 (file)
@@ -1443,15 +1443,9 @@ msgstr "Slot value must be integer."
 msgid "Slots for label have to contain string value from set [0-9-,]."
 msgstr "Slots for label have to contain string value from set [0-9-,]."
 
-msgid "Slots for label:"
-msgstr "Slots for label:"
-
 msgid "Slots for update have to contain string value from set [0-9-,]."
 msgstr "Slots for update have to contain string value from set [0-9-,]."
 
-msgid "Slots for update:"
-msgstr "Slots for update:"
-
 msgid "Source parameters"
 msgstr "リストア情報"
 
@@ -2130,3 +2124,27 @@ msgstr "Input must be between 1 and 1000."
 
 msgid "Schedule status"
 msgstr "Schedule status"
+
+msgid "Slots to label (ex. 4 or 1-5 or 2,4,6-10):"
+msgstr "Slots to label (ex. 4 or 1-5 or 2,4,6-10):"
+
+msgid "Labeling status:"
+msgstr "Labeling status:"
+
+msgid "Ready to label"
+msgstr "Ready to label"
+
+msgid "Finished"
+msgstr "Finished"
+
+msgid "Updating status:"
+msgstr "Updating status:"
+
+msgid "Ready to update"
+msgstr "Ready to update"
+
+msgid "Updating..."
+msgstr "Updating..."
+
+msgid "Slots to update (ex. 4 or 1-5 or 2,4,6-10):"
+msgstr "Slots to update (ex. 4 or 1-5 or 2,4,6-10):"
index 281a5935e52836cd9c13d52529b56c584d8ef688..dceb53f4402b8aad9dd57f5cc2bf8f06c6197cf2 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ
index e0eed90f309870a09b3d3f38920fa2b40c155e1b..a567c71f9791478d119916244d16b7b5e9445697 100644 (file)
@@ -476,18 +476,12 @@ msgstr "Użyj barcode jako etykiet"
 msgid "Label name:"
 msgstr "Nazwa etykiety:"
 
-msgid "Slots for label:"
-msgstr "Sloty do etykietowania:"
-
 msgid "Label"
 msgstr "Etykieta"
 
 msgid "Update slots using barcodes"
 msgstr "Aktualizuj sloty używajÄ…c barcode"
 
-msgid "Slots for update:"
-msgstr "Sloty do aktualizacji"
-
 msgid "Update barcodes slots"
 msgstr "Aktualizuj sloty z etykietami barcode"
 
@@ -2036,3 +2030,27 @@ msgstr "Pole musi mieć wartość z zakresu 1-1000."
 
 msgid "Schedule status"
 msgstr "Status harmonogramu zadaÅ„"
+
+msgid "Slots to label (ex. 4 or 1-5 or 2,4,6-10):"
+msgstr "Sloty do etykietowania (np. 4 lub 1-5 lub 2,4,6-10):"
+
+msgid "Labeling status:"
+msgstr "Status etykietowania:"
+
+msgid "Ready to label"
+msgstr "Gotowy do etykietowania"
+
+msgid "Finished"
+msgstr "ZakoÅ„czony"
+
+msgid "Updating status:"
+msgstr "Status aktualizowania:"
+
+msgid "Ready to update"
+msgstr "Gotowy do aktualizacji"
+
+msgid "Updating..."
+msgstr "Aktualizowanie..."
+
+msgid "Slots to update (ex. 4 or 1-5 or 2,4,6-10):"
+msgstr "Sloty do aktualizacji (np. 4 lub 1-5 lub 2,4,6-10):"
index fb7d7d7412b718d100880caed39c110a347d6488..83f3c0d1fb1d42b0a9d41a0afaf3c29a3a07f5bf 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ
index 50bb4272f47b6e768abde4feef68a00364406ec0..9ca157c32cd402878c581c806063f82b3bb339a5 100644 (file)
@@ -606,12 +606,6 @@ msgstr "Aplicar rótulo"
 msgid "Use barcodes as label:"
 msgstr "Usar código de barras como rótulos:"
 
-msgid "Slots for label:"
-msgstr "Slots para rotular:"
-
-msgid "Slots for update:"
-msgstr "Slots para atualizar:"
-
 msgid "Update barcodes slots"
 msgstr "Atualizar código de barras dos slots"
 
@@ -2044,3 +2038,27 @@ msgstr "Input must be between 1 and 1000."
 
 msgid "Schedule status"
 msgstr "Schedule status"
+
+msgid "Slots to label (ex. 4 or 1-5 or 2,4,6-10):"
+msgstr "Slots para rotular (ex. 4 or 1-5 or 2,4,6-10):"
+
+msgid "Labeling status:"
+msgstr "Labeling status:"
+
+msgid "Ready to label"
+msgstr "Ready to label"
+
+msgid "Finished"
+msgstr "Finished"
+
+msgid "Updating status:"
+msgstr "Updating status:"
+
+msgid "Ready to update"
+msgstr "Ready to update"
+
+msgid "Updating..."
+msgstr "Updating..."
+
+msgid "Slots to update (ex. 4 or 1-5 or 2,4,6-10):"
+msgstr "Slots to update (ex. 4 or 1-5 or 2,4,6-10):"
index 7ca4ac0748cd12d046ba0332ea732e85084f7965..544f6ffafd6ae81ed11c5088785d033daeba7187 100644 (file)
@@ -62,6 +62,13 @@ class LabelVolume extends Portlets {
                                'poolid' => $this->PoolLabel->SelectedValue
                        );
                        $result = $this->getModule('api')->create(array('volumes', 'label', 'barcodes'), $params);
+                       if ($result->error === 0 && count($result->output) === 1) {
+                               $out = json_decode($result->output[0]);
+                               if (is_object($out) && property_exists($out, 'out_id')) {
+                                       $result = $this->getLabelBarcodesOutput($out->out_id);
+                                       $this->getPage()->getCallbackClient()->callClientFunction('label_volume_output_refresh', array($out->out_id));
+                               }
+                       }
                } else {
                        $params = array(
                                'slot' => $this->SlotLabel->Text,
@@ -71,9 +78,52 @@ class LabelVolume extends Portlets {
                                'poolid' => $this->PoolLabel->SelectedValue
                        );
                        $result = $this->getModule('api')->create(array('volumes', 'label'), $params);
+                       if ($result->error === 0 && count($result->output) === 1) {
+                               $out = json_decode($result->output[0]);
+                               if (is_object($out) && property_exists($out, 'out_id')) {
+                                       $result = $this->getLabelOutput($out->out_id);
+                                       $this->getPage()->getCallbackClient()->callClientFunction('label_volume_output_refresh', array($out->out_id));
+                               }
+                       }
+               }
+               if ($result->error === 0) {
+                       $this->getPage()->getCallbackClient()->callClientFunction('set_labeling_status', array('loading'));
+                       $this->LabelVolumeLog->Text = implode('', $result->output);
+               } else {
+                       $this->LabelVolumeLog->Text = $result->output;
+               }
+       }
+
+       private function getLabelOutput($out_id) {
+               $result = $this->getModule('api')->get(
+                       array('volumes', 'label', '?out_id=' . rawurlencode($out_id))
+               );
+               return $result;
+       }
+
+       private function getLabelBarcodesOutput($out_id) {
+               $result = $this->getModule('api')->get(
+                       array('volumes', 'label', 'barcodes', '?out_id=' . rawurlencode($out_id))
+               );
+               return $result;
+       }
+
+       public function refreshOutput($sender, $param) {
+               $out_id = $param->getCallbackParameter();
+               $result = null;
+               if ($this->Barcodes->Checked == true) {
+                       $result = $this->getLabelBarcodesOutput($out_id);
+               } else {
+                       $result = $this->getLabelOutput($out_id);
                }
+
                if ($result->error === 0) {
-                       $this->LabelVolumeLog->Text = implode(PHP_EOL, $result->output);
+                       if (count($result->output) > 0) {
+                               $this->LabelVolumeLog->Text = implode('', $result->output);
+                               $this->getPage()->getCallbackClient()->callClientFunction('label_volume_output_refresh', array($out_id));
+                       } else {
+                               $this->getPage()->getCallbackClient()->callClientFunction('set_labeling_status', array('finish'));
+                       }
                } else {
                        $this->LabelVolumeLog->Text = $result->output;
                }
index 5589ba24db774bb11aaf76eb6c3263d3646e6744..0c5e78d29a486e6c0cc675c30a39310fc80f0511 100644 (file)
@@ -1,8 +1,15 @@
 <com:TActiveLinkButton
        CssClass="w3-button w3-green"
-       Attributes.onclick="document.getElementById('label_volume').style.display = 'block';"
        OnClick="loadValues"
 >
+       <prop:Attributes.onclick>
+               var logbox = document.getElementById('<%=$this->LabelVolumeLog->ClientID%>');
+               logbox.innerHTML = '';
+               var logbox_container = document.getElementById('label_volume_log');
+               logbox_container.style.display = 'none';
+               set_labeling_status('start');
+               document.getElementById('label_volume').style.display = 'block';
+       </prop:Attributes.onclick>
        <i class="fa fa-tag"></i> &nbsp;<%[ Label volume(s) ]%>
 </com:TActiveLinkButton>
 </button>
@@ -68,7 +75,7 @@
                                </div>
                        </div>
                        <div id="label_with_barcodes" class="w3-row-padding w3-section" style="display: none">
-                               <div class="w3-col w3-half"><com:TLabel ForControl="SlotsLabel" Text="<%[ Slots for label: ]%>" /></div>
+                               <div class="w3-col w3-half"><com:TLabel ForControl="SlotsLabel" Text="<%[ Slots to label (ex. 4 or 1-5 or 2,4,6-10): ]%>" /></div>
                                <div class="w3-col w3-half">
                                        <com:TActiveTextBox ID="SlotsLabel" CssClass="w3-input w3-border" Text="0" />
                                        <com:TRequiredFieldValidator
                                        </com:TRequiredFieldValidator>
                                </div>
                        </div>
+                       <div class="w3-row-padding w3-section">
+                               <div class="w3-col w3-half"><%[ Labeling status: ]%></div>
+                               <div class="w3-col w3-half">
+                                       <i id="label_status_start" class="fa fa-step-forward" title="<%[ Ready to label ]%>"></i>
+                                       <i id="label_status_loading" class="fa fa-sync w3-spin" style="display: none" title="<%[ Loading... ]%>"></i>
+                                       <i id="label_status_finish" class="fa fa-check" style="display: none" title="<%[ Finished ]%>"></i>
+                               </div>
+                       </div>
                        <div id="label_volume_log" class="w3-panel w3-card w3-light-grey" style="display: none; max-height: 200px; overflow-x: auto;">
                                <div class="w3-code notranslate">
                                        <pre><com:TActiveLabel ID="LabelVolumeLog" /></pre>
                                        ValidationGroup="LabelVolumeGroup"
                                        OnClick="labelVolumes"
                                        CssClass="w3-button w3-green"
-                                       ClientSide.OnLoading="$('#status_label_volume_loading').show();"
-                                       ClientSide.OnSuccess="$('#status_label_volume_loading').hide();$('#label_volume_log').show();"
+                                       ClientSide.OnLoading="$('#status_label_volume_loading').css('visibility', 'visible');"
                                >
+                                       <prop:ClientSide.OnComplete>
+                                               $('#status_label_volume_loading').css('visibility', 'hidden');
+                                               $('#label_volume_log').show();
+                                               var logbox = document.getElementById('label_volume_log');
+                                               logbox.scrollTo(0, logbox.scrollHeight);
+                                       </prop:ClientSide.OnComplete>
                                        <i class="fa fa-tag"></i> &nbsp;<%[ Label ]%>
                                </com:TActiveLinkButton>
-                               <i id="status_label_volume_loading" class="fa fa-sync w3-spin" style="display: none;"></i>
+                               <i id="status_label_volume_loading" class="fa fa-sync w3-spin" style="visibility: hidden;"></i>
                        </div>
                </div>
        </div>
 </div>
+<com:TCallback ID="LabelVolumeOutputRefresh"
+       OnCallback="refreshOutput"
+>
+       <prop:ClientSide.OnLoading>
+               $('#status_label_volume_loading').css('visibility', 'visible');
+               var logbox = document.getElementById('label_volume_log');
+               if ((logbox.offsetHeight + logbox.scrollTop) === logbox.scrollHeight) {
+                       label_volume_logbox_scroll = true;
+               } else {
+                       label_volume_logbox_scroll = false;
+               }
+       </prop:ClientSide.OnLoading>
+       <prop:ClientSide.OnComplete>
+               $('#status_label_volume_loading').css('visibility', 'hidden');
+               if (label_volume_logbox_scroll) {
+                       var logbox = document.getElementById('label_volume_log');
+                       logbox.scrollTo(0, logbox.scrollHeight);
+               }
+       </prop:ClientSide.OnComplete>
+</com:TCallback>
 <script type="text/javascript">
+var label_volume_logbox_scroll = false;
 function set_barcodes() {
        var chkb = document.getElementById('<%=$this->Barcodes->ClientID%>');
        var name_el = document.getElementById('label_with_name');
@@ -158,4 +199,35 @@ function set_barcodes() {
                name_el.style.display = 'block';
        }
 }
+
+function set_labeling_status(status) {
+       var start = document.getElementById('label_status_start');
+       var loading = document.getElementById('label_status_loading');
+       var finish = document.getElementById('label_status_finish');
+       if (status === 'finish') {
+               start.style.display = 'none';
+               loading.style.display = 'none';
+               finish.style.display = '';
+       } else if (status === 'loading') {
+               start.style.display = 'none';
+               loading.style.display = '';
+               finish.style.display = 'none';
+       } else if (status === 'start') {
+               start.style.display = '';
+               loading.style.display = 'none';
+               finish.style.display = 'none';
+       }
+}
+
+function label_volume_output_refresh(out_id) {
+       setTimeout(function() {
+               set_label_volume_output(out_id)
+       }, 3000);
+}
+
+function set_label_volume_output(out_id) {
+       var cb = <%=$this->LabelVolumeOutputRefresh->ActiveControl->Javascript%>;
+       cb.setCallbackParameter(out_id);
+       cb.dispatch();
+}
 </script>
index ae87183c56b6e5ea0248a4ad8c0001c4d45d0335..f110db129abf644b47e2c9c3cc13b2fd5085bb5b 100644 (file)
@@ -41,17 +41,65 @@ class UpdateSlots extends Portlets {
        }
 
        public function update($sender, $param) {
-               $cmd = array('label');
-               $cmd = array('update');
-               $cmd[] = 'slots="' . $this->SlotsUpdate->Text . '"';
-               if($this->Barcodes->Checked == false) {
-                       $cmd[] = 'scan';
+               $url_params = array();
+               if($this->Barcodes->Checked == true) {
+                       $url_params = array('volumes', 'update', 'barcodes');
+               } else {
+                       $url_params = array('volumes', 'update');
+               }
+               $params = array(
+                       'slots' => $this->SlotsUpdate->Text,
+                       'drive' => $this->DriveUpdate->Text,
+                       'storageid' => $this->StorageUpdate->SelectedValue
+               );
+
+               $result = $this->getModule('api')->set($url_params, $params);
+
+               if ($result->error === 0 && count($result->output) === 1) {
+                       $out = json_decode($result->output[0]);
+                       if (is_object($out) && property_exists($out, 'out_id')) {
+                               $result = $this->getUpdateSlotsOutput($out->out_id);
+                               $this->getPage()->getCallbackClient()->callClientFunction('update_slots_output_refresh', array($out->out_id));
+                       }
                }
-               $cmd[] = 'drive="' . $this->DriveUpdate->Text . '"';
-               $cmd[] = 'storage="'. $this->StorageUpdate->SelectedItem->Text . '"';
-               $result = $this->getModule('api')->set(array('console'), $cmd);
                if ($result->error === 0) {
-                       $this->UpdateSlotsLog->Text = implode(PHP_EOL, $result->output);
+                       $this->getPage()->getCallbackClient()->callClientFunction('set_updating_status', array('loading'));
+                       $this->UpdateSlotsLog->Text = implode('', $result->output);
+               } else {
+                       $this->UpdateSlotsLog->Text = $result->output;
+               }
+       }
+
+       private function getUpdateSlotsOutput($out_id) {
+               $result = $this->getModule('api')->get(
+                       array('volumes', 'update', '?out_id=' . rawurlencode($out_id))
+               );
+               return $result;
+       }
+
+       private function getUpdateSlotsBarcodesOutput($out_id) {
+               $result = $this->getModule('api')->get(
+                       array('volumes', 'update', 'barcodes', '?out_id=' . rawurlencode($out_id))
+               );
+               return $result;
+       }
+
+       public function refreshOutput($sender, $param) {
+               $out_id = $param->getCallbackParameter();
+               $result = null;
+               if ($this->Barcodes->Checked == true) {
+                       $result = $this->getUpdateSlotsBarcodesOutput($out_id);
+               } else {
+                       $result = $this->getUpdateSlotsOutput($out_id);
+               }
+
+               if ($result->error === 0) {
+                       if (count($result->output) > 0) {
+                               $this->UpdateSlotsLog->Text = implode('', $result->output);
+                               $this->getPage()->getCallbackClient()->callClientFunction('update_slots_output_refresh', array($out_id));
+                       } else {
+                               $this->getPage()->getCallbackClient()->callClientFunction('set_updating_status', array('finish'));
+                       }
                } else {
                        $this->UpdateSlotsLog->Text = $result->output;
                }
index 30d8616dbfb637a7bb3751e12c06256acbb57040..aaca9d07a8f95eea7f71bc919a576cbc9e335d50 100644 (file)
@@ -1,8 +1,15 @@
 <com:TActiveLinkButton
        CssClass="w3-button w3-green"
-       Attributes.onclick="document.getElementById('update_slots').style.display = 'block';"
        OnClick="loadValues"
 >
+       <prop:Attributes.onclick>
+               var logbox = document.getElementById('<%=$this->UpdateSlotsLog->ClientID%>');
+               logbox.innerHTML = '';
+               var logbox_container = document.getElementById('update_slots_log');
+               logbox_container.style.display = 'none';
+               set_updating_status('start');
+               document.getElementById('update_slots').style.display = 'block';
+       </prop:Attributes.onclick>
        <i class="fa fa-retweet"></i> &nbsp;<%[ Update slots ]%>
 </com:TActiveLinkButton>
 </button>
@@ -48,7 +55,7 @@
                                <div class="w3-col w3-half"><com:TActiveDropDownList ID="StorageUpdate" CssClass="w3-select w3-border" /></div>
                        </div>
                        <div class="w3-row-padding w3-section">
-                               <div class="w3-col w3-half"><com:TLabel ForControl="SlotsUpdate" Text="<%[ Slots for update: ]%>" /></div>
+                               <div class="w3-col w3-half"><com:TLabel ForControl="SlotsUpdate" Text="<%[ Slots to update (ex. 4 or 1-5 or 2,4,6-10): ]%>" /></div>
                                <div class="w3-col w3-half">
                                        <com:TActiveTextBox ID="SlotsUpdate" CssClass="w3-input w3-border" Text="0" />
                                        <com:TRequiredFieldValidator
                                        />
                                </div>
                        </div>
+                       <div class="w3-row-padding w3-section">
+                               <div class="w3-col w3-half"><%[ Updating status: ]%></div>
+                               <div class="w3-col w3-half">
+                                       <i id="update_slots_status_start" class="fa fa-step-forward" title="<%[ Ready to update ]%>"></i>
+                                       <i id="update_slots_status_loading" class="fa fa-sync w3-spin" style="display: none" title="<%[ Updating... ]%>"></i>
+                                       <i id="update_slots_status_finish" class="fa fa-check" style="display: none" title="<%[ Finished ]%>"></i>
+                               </div>
+                       </div>
                        <div id="update_slots_log" class="w3-panel w3-card w3-light-grey" style="display: none; max-height: 200px; overflow-x: auto;">
                                <div class="w3-code notranslate">
                                        <pre><com:TActiveLabel ID="UpdateSlotsLog" /></pre>
                                        OnClick="update"
                                        CssClass="w3-button w3-green"
                                        ClientSide.OnLoading="$('#status_update_slots_loading').show();"
-                                       ClientSide.OnSuccess="$('#status_update_slots_loading').hide();$('#update_slots_log').show();"
                                >
+                                       <prop:ClientSide.OnComplete>
+                                               $('#status_update_slots_loading').css('visibility', 'hidden');
+                                               $('#update_slots_log').show();
+                                               var logbox = document.getElementById('update_slots_log');
+                                               logbox.scrollTo(0, logbox.scrollHeight);
+                                       </prop:ClientSide.OnComplete>
                                        <i class="fa fa-retweet"></i> &nbsp;<%[ Update slots ]%>
                                </com:TActiveLinkButton>
-                               <i id="status_update_slots_loading" class="fa fa-sync w3-spin" style="display: none;"></i>
+                               <i id="status_update_slots_loading" class="fa fa-sync w3-spin" style="visibility: hidden;"></i>
                        </div>
                </div>
        </div>
 </div>
+<com:TCallback ID="UpdateSlotsOutputRefresh"
+       OnCallback="refreshOutput"
+>
+       <prop:ClientSide.OnLoading>
+               $('#status_update_slots_loading').css('visibility', 'visible');
+               var logbox = document.getElementById('update_slots_log');
+               if ((logbox.offsetHeight + logbox.scrollTop) === logbox.scrollHeight) {
+                       update_slots_logbox_scroll = true;
+               } else {
+                       update_slots_logbox_scroll = false;
+               }
+       </prop:ClientSide.OnLoading>
+       <prop:ClientSide.OnComplete>
+               $('#status_update_slots_loading').css('visibility', 'hidden');
+               if (update_slots_logbox_scroll) {
+                       var logbox = document.getElementById('update_slots_log');
+                       logbox.scrollTo(0, logbox.scrollHeight);
+               }
+       </prop:ClientSide.OnComplete>
+</com:TCallback>
+<script type="text/javascript">
+var update_slots_logbox_scroll = false;
+function set_updating_status(status) {
+       var start = document.getElementById('update_slots_status_start');
+       var loading = document.getElementById('update_slots_status_loading');
+       var finish = document.getElementById('update_slots_status_finish');
+       if (status === 'finish') {
+               start.style.display = 'none';
+               loading.style.display = 'none';
+               finish.style.display = '';
+       } else if (status === 'loading') {
+               start.style.display = 'none';
+               loading.style.display = '';
+               finish.style.display = 'none';
+       } else if (status === 'start') {
+               start.style.display = '';
+               loading.style.display = 'none';
+               finish.style.display = 'none';
+       }
+}
+
+function update_slots_output_refresh(out_id) {
+       setTimeout(function() {
+               set_update_slots_output(out_id)
+       }, 3000);
+}
+
+function set_update_slots_output(out_id) {
+       var cb = <%=$this->UpdateSlotsOutputRefresh->ActiveControl->Javascript%>;
+       cb.setCallbackParameter(out_id);
+       cb.dispatch();
+}
+</script>