From: Marcin Haba Date: Sun, 4 Apr 2021 21:01:49 +0000 (+0200) Subject: baculum: Implement autochanger management X-Git-Tag: Release-11.0.3~27 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=217d27166f2e56a0f93ca16352baded79f178771;p=thirdparty%2Fbacula.git baculum: Implement autochanger management --- diff --git a/gui/baculum/examples/selinux/baculum-api.te b/gui/baculum/examples/selinux/baculum-api.te index bf1c53e5f..e7ceecd0a 100644 --- a/gui/baculum/examples/selinux/baculum-api.te +++ b/gui/baculum/examples/selinux/baculum-api.te @@ -1,4 +1,4 @@ -module baculum-api 1.0.2; +module baculum-api 1.0.3; require { type init_t; @@ -12,21 +12,30 @@ require { type httpd_cache_t; type bacula_etc_t; type bacula_exec_t; + type bacula_spool_t; type httpd_sys_rw_content_t; + type scsi_generic_device_t; type shadow_t; type systemd_systemctl_exec_t; + type systemd_logind_sessions_t; + type systemd_logind_t; type systemd_unit_file_t; type admin_home_t; type usr_t; type postfix_etc_t; + type initrc_var_run_t; + type tape_device_t; class tcp_socket { name_bind name_connect }; - class dir { search read write create }; - class file { append read write create getattr open execute execute_no_trans }; + class dir { search read write create add_name remove_name }; + class file { append read write create getattr setattr open execute execute_no_trans ioctl unlink lock rename }; + class chr_file { open read write ioctl }; + class fifo_file { write }; class netlink_audit_socket { write nlmsg_relay create read }; class capability { audit_write sys_resource net_admin }; class service { start stop }; class unix_stream_socket { connectto }; class process { setrlimit }; + class dbus { send_msg }; } #============= httpd_t ============== @@ -38,6 +47,9 @@ allow httpd_t hplip_port_t:tcp_socket name_connect; allow httpd_t bacula_etc_t:dir { read write search }; allow httpd_t bacula_etc_t:file { getattr read write open }; allow httpd_t bacula_exec_t:file { getattr read execute execute_no_trans open }; +allow httpd_t bacula_spool_t:dir { write add_name remove_name }; +allow httpd_t bacula_spool_t:file { getattr create open read write ioctl unlink }; +allow httpd_t scsi_generic_device_t:chr_file { open read write ioctl }; allow httpd_t sudo_exec_t:file { read execute open }; allow httpd_t httpd_cache_t:dir { read create }; allow httpd_t httpd_cache_t:file { read write create }; @@ -48,8 +60,16 @@ allow httpd_t httpd_sys_rw_content_t:dir { read write }; allow httpd_t httpd_sys_rw_content_t:file { create append }; allow httpd_t shadow_t:file { open read getattr }; allow httpd_t systemd_systemctl_exec_t:file { getattr open read execute execute_no_trans }; +allow httpd_t systemd_logind_sessions_t:fifo_file write; +allow httpd_t systemd_logind_t:dbus send_msg; allow httpd_t systemd_unit_file_t:service { start stop }; allow httpd_t init_t:unix_stream_socket connectto; -allow httpd_t admin_home_t:file { getattr open read append write }; +allow httpd_t admin_home_t:dir { write add_name remove_name }; +allow httpd_t admin_home_t:file { getattr setattr create open read append write rename unlink }; allow httpd_t usr_t:file write; allow httpd_t postfix_etc_t:file read; +allow httpd_t initrc_var_run_t:file { open read lock }; +allow httpd_t tape_device_t:chr_file read; + +#============= systemd_logind_t ============== +allow systemd_logind_t httpd_t:dbus send_msg; diff --git a/gui/baculum/protected/API/Class/ChangerCommand.php b/gui/baculum/protected/API/Class/ChangerCommand.php new file mode 100644 index 000000000..490f3e137 --- /dev/null +++ b/gui/baculum/protected/API/Class/ChangerCommand.php @@ -0,0 +1,366 @@ + + * @category Autochanger + * @package Baculum API + */ +class ChangerCommand extends APIModule { + + const SUDO = 'sudo'; + + /** + * Types to determine how changer command is executed (foreground, background...) + */ + const PTYPE_FG_CMD = 0; + const PTYPE_BG_CMD = 1; + + /** + * Output file prefix used to temporary store output from commands. + */ + const OUTPUT_FILE_PREFIX = 'output_'; + + /** + * Pattern to changer command. + */ + const CHANGER_COMMAND_FG_PATTERN = '%s %s 2>&1'; + const CHANGER_COMMAND_BG_PATTERN = '{ %s %s 1>%s 2>&1; echo "quit" >> %s ; } &'; + + /** + * Supported parameters with short codes. + * NOTE: order has meaning here. + */ + private $params = [ + '%c' => 'changer-device', + '%o' => 'command', + '%S' => 'slot', + '%a' => 'archive-device', + '%d' => 'drive-index' + ]; + + /** + * Supported changer commands. + */ + private $commands = [ + 'load', + 'unload', + 'loaded', + 'list', + 'slots', + 'listall', + 'transfer' + ]; + + /** + * Stores device config. + */ + private $config; + + public function init($param) { + $this->config = $this->getModule('device_config')->getConfig(); + } + + /** + * Validate changer script command. + * @param string $command script command + * @return boolean true on validation success, otherwise false + */ + private function validateCommand($command) { + return in_array($command, $this->commands); + } + + /** + * Get sudo command. + * + * @param boolean $use_sudo sudo option state + * @return string sudo command + */ + private function getSudo($use_sudo) { + $sudo = ''; + if ($use_sudo === true) { + $sudo = self::SUDO; + } + return $sudo; + } + + /** + * Execute changer command. + * + * @param string $changer autochanger device name + * @param string $command changer command (load, unload ...etc.) + * @param string $device archive device name (autochanger drive name) + * @param string $slot slot in slots magazine to use + * @param string $slotdest destination slot in slots magazine (used with transfer command) + * @param string $ptype command pattern type + * @return StdClass executed command output and error code + */ + public function execChangerCommand($changer, $command, $device = null, $slot = null, $slotdest = null, $ptype = null) { + if (!$this->validateCommand($command)) { + $output = DeviceError::MSG_ERROR_DEVICE_INVALID_COMMAND; + $error = DeviceError::ERROR_DEVICE_INVALID_COMMAND; + $result = $this->prepareResult($output, $error); + return $result; + } + if (count($this->config) == 0) { + $output = DeviceError::MSG_ERROR_DEVICE_DEVICE_CONFIG_DOES_NOT_EXIST; + $error = DeviceError::ERROR_DEVICE_DEVICE_CONFIG_DOES_NOT_EXIST; + $result = $this->prepareResult($output, $error); + return $result; + } + if (!key_exists($changer, $this->config) || $this->config[$changer]['type'] !== DeviceConfig::DEV_TYPE_AUTOCHANGER) { + $output = DeviceError::MSG_ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST; + $error = DeviceError::ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST; + $result = $this->prepareResult($output, $error); + return $result; + } + if (is_string($device)) { + $drives = explode(',', $this->config[$changer]['drives']); + if (!in_array($device, $drives)) { + $output = DeviceError::MSG_ERROR_DEVICE_DRIVE_DOES_NOT_BELONG_TO_AUTOCHANGER; + $error = DeviceError::ERROR_DEVICE_DRIVE_DOES_NOT_BELONG_TO_AUTOCHANGER; + $result = $this->prepareResult($output, $error); + return $result; + } + } + + if (is_string($device) && (!key_exists($device, $this->config) || $this->config[$device]['type'] !== DeviceConfig::DEV_TYPE_DEVICE)) { + $output = DeviceError::MSG_ERROR_DEVICE_AUTOCHANGER_DRIVE_DOES_NOT_EXIST; + $error = DeviceError::ERROR_DEVICE_AUTOCHANGER_DRIVE_DOES_NOT_EXIST; + $result = $this->prepareResult($output, $error); + return $result; + } + $changer_command = $this->config[$changer]['command']; + $changer_device = $this->config[$changer]['device']; + $archive_device = is_string($device) ? $this->config[$device]['device'] : ''; + $drive_index = is_string($device) ? $this->config[$device]['index'] : ''; + $use_sudo = ($this->config[$changer]['use_sudo'] == 1); + + if ($command === 'transfer') { + // in transfer command in place archive device is given destination slot + $archive_device = $slotdest; + } + + $command = $this->prepareChangerCommand( + $changer_command, + $changer_device, + $command, + $slot, + $archive_device, + $drive_index + ); + $pattern = $this->getCmdPattern($ptype); + $cmd = $this->getCommand($pattern, $use_sudo, $command); + $result = $this->execCommand($cmd, $ptype); + if ($result->error !== 0) { + $emsg = PHP_EOL . ' Output:' . implode(PHP_EOL, $result->output); + $output = DeviceError::MSG_ERROR_WRONG_EXITCODE . $emsg; + $exitcode = DeviceError::ERROR_WRONG_EXITCODE; + $result = $this->prepareResult($output, $exitcode); + } + return $result; + } + + /** + * Prepare changer command to execute. + * + * @param string $changer_command full changer command + * @param string $changer_device changer device name + * @param string $command changer command (load, unload ...etc.) + * @param string $slot slot in slots magazine to use + * @param string $archive_device archive device name (autochanger drive name) + * @param string $drive_index archive device index (autochanger drive index) + * @return StdClass executed command output and error code + */ + private function prepareChangerCommand($changer_command, $changer_device, $command, $slot, $archive_device, $drive_index) { + $from = array_keys($this->params); + $to = [ + '"' . $changer_device .'"', + '"' . $command .'"', + '"' . $slot .'"', + '"' . $archive_device .'"', + '"' . $drive_index .'"' + ]; + return str_replace($from, $to, $changer_command); + } + + /** + * Get changer command to execute. + * + * @param string $pattern changer command pattern (@see PTYPE_ constants) + * @param boolean $use_sudo information about using sudo + * @param string $bin changer command + * @return array changer command (and output id if selected pattern to + * move command to background) + */ + private function getCommand($pattern, $use_sudo, $bin) { + $command = array('cmd' => null, 'out_id' => null); + $misc = $this->getModule('misc'); + $sudo = $this->getSudo($use_sudo); + + if ($pattern === self::CHANGER_COMMAND_BG_PATTERN) { + $file = $this->prepareOutputFile(); + $cmd = sprintf( + $pattern, + $sudo, + $bin, + $file, + $file + ); + $command['cmd'] = $misc->escapeCharsToConsole($cmd); + $command['out_id'] = preg_replace('/^[\s\S]+\/' . self::OUTPUT_FILE_PREFIX . '/', '', $file); + } else { + $cmd = sprintf($pattern, $sudo, $bin); + $command['cmd'] = $misc->escapeCharsToConsole($cmd); + $command['out_id'] = ''; + } + return $command; + } + + /** + * Create and get output file. + * Used with background type command patterns (ex. PTYPE_BG_CMD) + * + * @return string|boolean new temporary filename (with path), or false on failure. + */ + private function prepareOutputFile() { + $dir = Prado::getPathOfNamespace('Application.API.Config'); + $fname = tempnam($dir, self::OUTPUT_FILE_PREFIX); + return $fname; + } + + /** + * Read output file and return the output. + * Used with background type command patterns (ex. PTYPE_BG_CMD) + * + * @param string $out_id command output identifier + * @return array command output with one line per one array element + */ + public static function readOutputFile($out_id) { + $output = []; + $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; + } + + /** + * Execute changer command. + * + * @param string $bin command + * @param string $ptype command pattern type + * @return array result with output and error code + */ + public function execCommand($cmd, $ptype = null) { + exec($cmd['cmd'], $output, $exitcode); + $this->getModule('logging')->log( + $cmd['cmd'], + $output, + Logging::CATEGORY_EXECUTE, + __FILE__, + __LINE__ + ); + if ($ptype === self::PTYPE_BG_CMD) { + $output = [ + 'out_id' => $cmd['out_id'] + ]; + } + return $this->prepareResult($output, $exitcode); + } + + /** + * Prepare changer command result. + * + * @param array $output output from command execution + * @param integer $error command error code + * @return array result with output and error code + */ + public function prepareResult($output, $error) { + $result = new StdClass; + $result->output = $output; + $result->error = $error; + return $result; + } + + /** + * Get command pattern by ptype. + * + * @param string $ptype pattern type (@see PTYPE_ constants) + * @return string command pattern + */ + private function getCmdPattern($ptype) { + $pattern = null; + switch ($ptype) { + case self::PTYPE_FG_CMD: $pattern = self::CHANGER_COMMAND_FG_PATTERN; break; + case self::PTYPE_BG_CMD: $pattern = self::CHANGER_COMMAND_BG_PATTERN; break; + default: $pattern = self::CHANGER_COMMAND_FG_PATTERN; + } + return $pattern; + } + + /** + * Check changer command parameters. + * Used to test parameters. + * + * @param boolean $use_sudo information about using sudo + * @param string $changer_command full changer command + * @param string $changer_device changer device name + * @param string $command changer command (load, unload ...etc.) + * @param string $slot slot in slots magazine to use + * @param string $archive_device archive device name (autochanger drive name) + * @param string $drive_index archive device index (autochanger drive index) + * @return StdClass executed command output and error code + */ + public function testChangerCommand($use_sudo, $changer_command, $changer_device, $command, $slot, $archive_device, $drive_index) { + $command = $this->prepareChangerCommand( + $changer_command, + $changer_device, + $command, + $slot, + $archive_device, + $drive_index + ); + $pattern = $this->getCmdPattern(self::PTYPE_FG_CMD); + $cmd = $this->getCommand($pattern, $use_sudo, $command); + $result = $this->execCommand($cmd, self::PTYPE_FG_CMD); + return $result; + } +} +?> diff --git a/gui/baculum/protected/API/Class/DeviceConfig.php b/gui/baculum/protected/API/Class/DeviceConfig.php new file mode 100644 index 000000000..cdc92be7c --- /dev/null +++ b/gui/baculum/protected/API/Class/DeviceConfig.php @@ -0,0 +1,147 @@ + + * @category Device + * @package Baculum API + */ +class DeviceConfig extends ConfigFileModule { + + /** + * Supported device types + */ + const DEV_TYPE_DEVICE = 'device'; + const DEV_TYPE_AUTOCHANGER = 'autochanger'; + + /** + * Device file path patter. + */ + const DEVICE_PATH_PATTERN = '[a-zA-Z0-9:.\-_ ]+'; + + /** + * Device config file path + */ + const CONFIG_FILE_PATH = 'Application.API.Config.devices'; + + /** + * Device config file format + */ + const CONFIG_FILE_FORMAT = 'ini'; + + /** + * Stores device config. + */ + private $config = null; + + /** + * These options are obligatory for device config. + */ + private $required_options = ['type', 'device']; + + /** + * Get (read) device config. + * + * @param string $section config section name + * @return array config + */ + public function getConfig($section = null) { + $config = []; + if (is_null($this->config)) { + $this->config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); + } + $is_valid = true; + if (!is_null($section)) { + $config = key_exists($section, $this->config) ? $this->config[$section] : []; + $is_valid = $this->validateConfig($section, $config); + } else { + foreach ($this->config as $section => $value) { + if ($this->validateConfig($section, $value) === false) { + $is_valid = false; + break; + } + $config[$section] = $value; + } + } + if ($is_valid === false) { + // no validity, no config + $config = []; + } + return $config; + } + + /** + * Set (save) device client config. + * + * @param array $config config + * @return boolean true if config saved successfully, otherwise false + */ + public function setConfig(array $config) { + $result = $this->writeConfig($config, self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); + if ($result === true) { + $this->config = null; + } + return $result; + } + + + /** + * Validate device config. + * Config validation should be used as early as config data is available. + * Validation is done in read/write config methods. + * + * @param string $section section name + * @param array $config config + * @return boolean true if config valid, otherwise false + */ + private function validateConfig($section, array $config = []) { + $required_options = [$section => $this->required_options]; + return $this->isConfigValid( + $required_options, + [$section => $config], + self::CONFIG_FILE_FORMAT, + self::CONFIG_FILE_PATH + ); + } + + public function getChangerDrives($changer) { + $drives = []; + $config = $this->getConfig($changer); + if (count($config) > 0) { + $ach_drives = explode(',', $config['drives']); + for ($i = 0; $i < count($ach_drives); $i++) { + $drive = $this->getConfig($ach_drives[$i]); + if (count($drive) > 0 && $drive['type'] === self::DEV_TYPE_DEVICE) { + $drive['name'] = $ach_drives[$i]; + $drives[$drive['index']] = $drive; + } + } + } + return $drives; + } +} +?> diff --git a/gui/baculum/protected/API/Class/VolumeManager.php b/gui/baculum/protected/API/Class/VolumeManager.php index 1ffc1393c..69dcf599e 100644 --- a/gui/baculum/protected/API/Class/VolumeManager.php +++ b/gui/baculum/protected/API/Class/VolumeManager.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2021 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -175,5 +175,22 @@ LEFT JOIN Storage USING (StorageId) } return $volumes; } + + /** + * Get volumes basing on specific criteria and return results as an array + * with volume names as keys. + * + * @param array $criteria array with criterias (@see VolumeManager::getVolumes) + * @param integer $limit_val limit results value + * @return array volume list with volume names as keys + */ + public function getVolumesKeys($criteria = array(), $limit_val = 0) { + $volumes = []; + $vols = $this->getVolumes($criteria, $limit_val); + for ($i = 0; $i < count($vols); $i++) { + $volumes[$vols[$i]->volumename] = $vols[$i]; + } + return $volumes; + } } ?> diff --git a/gui/baculum/protected/API/Lang/en/messages.mo b/gui/baculum/protected/API/Lang/en/messages.mo index 701f1bc3f..b3e138344 100644 Binary files a/gui/baculum/protected/API/Lang/en/messages.mo and b/gui/baculum/protected/API/Lang/en/messages.mo differ diff --git a/gui/baculum/protected/API/Lang/en/messages.po b/gui/baculum/protected/API/Lang/en/messages.po index 0ea4a2c07..05cbdd01c 100644 --- a/gui/baculum/protected/API/Lang/en/messages.po +++ b/gui/baculum/protected/API/Lang/en/messages.po @@ -565,3 +565,75 @@ msgstr "Copy to clipboard" msgid "Logout" msgstr "Logout" + +msgid "Devices" +msgstr "Devices" + +msgid "Autochangers" +msgstr "Autochangers" + +msgid "Add autochanger" +msgstr "Add autochanger" + +msgid "Device" +msgstr "Device" + +msgid "Name:" +msgstr "Name:" + +msgid "Changer device:" +msgstr "Changer device:" + +msgid "Changer command:" +msgstr "Changer command:" + +msgid "Drive index:" +msgstr "Drive index:" + +msgid "Drive index" +msgstr "Drive index" + +msgid "Devices:" +msgstr "Devices:" + +msgid "Add device" +msgstr "Add device" + +msgid "Field cannot be empty." +msgstr "Field cannot be empty." + +msgid "Invalid value." +msgstr "Invalid value." + +msgid "Device with the given name already exists." +msgstr "Device with the given name already exists." + +msgid "Edit device" +msgstr "Edit device" + +msgid "Use CTRL + left-click to multiple item selection" +msgstr "Use CTRL + left-click to multiple item selection" + +msgid "Device path:" +msgstr "Device path:" + +msgid "Edit autochanger" +msgstr "Edit autochanger" + +msgid "Autochanger" +msgstr "Autochanger" + +msgid "Unable to delete device. Please unassign it from autochanger '%s' first." +msgstr "Unable to delete device. Please unassign it from autochanger '%s' first." + +msgid "Autochanger with the given name already exists." +msgstr "Autochanger with the given name already exists." + +msgid "Copy from Bacula SD config:" +msgstr "Copy from Bacula SD config:" + +msgid "Changer command test:" +msgstr "Changer command test:" + +msgid "Changer command error" +msgstr "Changer command error" diff --git a/gui/baculum/protected/API/Lang/pl/messages.mo b/gui/baculum/protected/API/Lang/pl/messages.mo index b05c80b8f..f9414fcd3 100644 Binary files a/gui/baculum/protected/API/Lang/pl/messages.mo and b/gui/baculum/protected/API/Lang/pl/messages.mo differ diff --git a/gui/baculum/protected/API/Lang/pl/messages.po b/gui/baculum/protected/API/Lang/pl/messages.po index e06c1c7f0..d7259ab09 100644 --- a/gui/baculum/protected/API/Lang/pl/messages.po +++ b/gui/baculum/protected/API/Lang/pl/messages.po @@ -572,3 +572,74 @@ msgstr "Kopiuj do schowka" msgid "Logout" msgstr "Wyloguj" +msgid "Devices" +msgstr "Urządzenia" + +msgid "Autochangers" +msgstr "Zmieniarki taśm" + +msgid "Add autochanger" +msgstr "Dodaj zmieniarkę taśm" + +msgid "Device" +msgstr "Urządzenie" + +msgid "Name:" +msgstr "Nazwa:" + +msgid "Changer device:" +msgstr "Urządzenie zmieniarki taśm:" + +msgid "Changer command:" +msgstr "Komenda zmieniarki taśm:" + +msgid "Drive index:" +msgstr "Indeks napędu:" + +msgid "Drive index" +msgstr "Indeks napędu" + +msgid "Devices:" +msgstr "Urządzenia:" + +msgid "Add device" +msgstr "Dodaj urządzenie" + +msgid "Field cannot be empty." +msgstr "Pole nie może być puste." + +msgid "Invalid value." +msgstr "Niepoprawna wartość." + +msgid "Device with the given name already exists." +msgstr "Urządzenie o podanej nazwie już istnieje." + +msgid "Edit device" +msgstr "Edytuj urządzenie" + +msgid "Use CTRL + left-click to multiple item selection" +msgstr "Aby zaznaczyć wiele elementów użyj CTRL + lewy-klik" + +msgid "Device path:" +msgstr "Ścieżka urządzenia:" + +msgid "Edit autochanger" +msgstr "Edytuj zmieniarkę taśm" + +msgid "Autochanger" +msgstr "Zmieniarka taśm" + +msgid "Unable to delete device. Please unassign it from autochanger '%s' first." +msgstr "Nie można usunąć urządzenia. Proszę napierw wypisać go ze zmieniarki taśm '%s'." + +msgid "Autochanger with the given name already exists." +msgstr "Zmieniarka taśm o podanej nazwie już istnieje." + +msgid "Copy from Bacula SD config:" +msgstr "Kopiuj z konfiguracji Bacula SD:" + +msgid "Changer command test:" +msgstr "Test komendy zmieniarki:" + +msgid "Changer command error" +msgstr "Błąd komendy zmieniarki" diff --git a/gui/baculum/protected/API/Lang/pt/messages.mo b/gui/baculum/protected/API/Lang/pt/messages.mo index 68ba56276..a64879fbe 100644 Binary files a/gui/baculum/protected/API/Lang/pt/messages.mo and b/gui/baculum/protected/API/Lang/pt/messages.mo differ diff --git a/gui/baculum/protected/API/Lang/pt/messages.po b/gui/baculum/protected/API/Lang/pt/messages.po index 5f895ba87..8cb00d291 100644 --- a/gui/baculum/protected/API/Lang/pt/messages.po +++ b/gui/baculum/protected/API/Lang/pt/messages.po @@ -572,3 +572,74 @@ msgstr "Copiar para área de transferência" msgid "Logout" msgstr "Sair" +msgid "Devices" +msgstr "Devices" + +msgid "Autochangers" +msgstr "Autochangers" + +msgid "Add autochanger" +msgstr "Add autochanger" + +msgid "Device" +msgstr "Device" + +msgid "Name:" +msgstr "Name:" + +msgid "Changer device:" +msgstr "Changer device:" + +msgid "Changer command:" +msgstr "Changer command:" + +msgid "Drive index:" +msgstr "Drive index:" + +msgid "Drive index" +msgstr "Drive index" + +msgid "Devices:" +msgstr "Devices:" + +msgid "Add device" +msgstr "Add device" + +msgid "Field cannot be empty." +msgstr "Field cannot be empty." + +msgid "Invalid value." +msgstr "Invalid value." + +msgid "Device with the given name already exists." +msgstr "Device with the given name already exists." + +msgid "Edit device" +msgstr "Edit device" + +msgid "Use CTRL + left-click to multiple item selection" +msgstr "Use CTRL + left-click to multiple item selection" + +msgid "Device path:" +msgstr "Device path:" + +msgid "Edit autochanger" +msgstr "Edit autochanger" + +msgid "Autochanger" +msgstr "Autochanger" + +msgid "Unable to delete device. Please unassign it from autochanger '%s' first." +msgstr "Unable to delete device. Please unassign it from autochanger '%s' first." + +msgid "Autochanger with the given name already exists." +msgstr "Autochanger with the given name already exists." + +msgid "Copy from Bacula SD config:" +msgstr "Copy from Bacula SD config:" + +msgid "Changer command test:" +msgstr "Changer command test:" + +msgid "Changer command error" +msgstr "Changer command error" diff --git a/gui/baculum/protected/API/Lang/ru/messages.mo b/gui/baculum/protected/API/Lang/ru/messages.mo index a983830d7..97d51451d 100644 Binary files a/gui/baculum/protected/API/Lang/ru/messages.mo and b/gui/baculum/protected/API/Lang/ru/messages.mo differ diff --git a/gui/baculum/protected/API/Lang/ru/messages.po b/gui/baculum/protected/API/Lang/ru/messages.po index 26aae30f6..c61cf7dfa 100644 --- a/gui/baculum/protected/API/Lang/ru/messages.po +++ b/gui/baculum/protected/API/Lang/ru/messages.po @@ -572,3 +572,74 @@ msgstr "Копировать в буфер обмена" msgid "Logout" msgstr "Выйти" +msgid "Devices" +msgstr "Devices" + +msgid "Autochangers" +msgstr "Autochangers" + +msgid "Add autochanger" +msgstr "Add autochanger" + +msgid "Device" +msgstr "Device" + +msgid "Name:" +msgstr "Name:" + +msgid "Changer device:" +msgstr "Changer device:" + +msgid "Changer command:" +msgstr "Changer command:" + +msgid "Drive index:" +msgstr "Drive index:" + +msgid "Drive index" +msgstr "Drive index" + +msgid "Devices:" +msgstr "Devices:" + +msgid "Add device" +msgstr "Add device" + +msgid "Field cannot be empty." +msgstr "Field cannot be empty." + +msgid "Invalid value." +msgstr "Invalid value." + +msgid "Device with the given name already exists." +msgstr "Device with the given name already exists." + +msgid "Edit device" +msgstr "Edit device" + +msgid "Use CTRL + left-click to multiple item selection" +msgstr "Use CTRL + left-click to multiple item selection" + +msgid "Device path:" +msgstr "Device path:" + +msgid "Edit autochanger" +msgstr "Edit autochanger" + +msgid "Autochanger" +msgstr "Autochanger" + +msgid "Unable to delete device. Please unassign it from autochanger '%s' first." +msgstr "Unable to delete device. Please unassign it from autochanger '%s' first." + +msgid "Autochanger with the given name already exists." +msgstr "Autochanger with the given name already exists." + +msgid "Copy from Bacula SD config:" +msgstr "Copy from Bacula SD config:" + +msgid "Changer command test:" +msgstr "Changer command test:" + +msgid "Changer command error" +msgstr "Changer command error" diff --git a/gui/baculum/protected/API/Pages/API/ChangerDriveLoad.php b/gui/baculum/protected/API/Pages/API/ChangerDriveLoad.php new file mode 100644 index 000000000..7c5e0731e --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/ChangerDriveLoad.php @@ -0,0 +1,75 @@ + + * @category API + * @package Baculum API + */ +class ChangerDriveLoad extends BaculumAPIServer { + + public function get() { + $output = []; + $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 = ChangerCommand::readOutputFile($out_id); + } + $this->output = $output; + $this->error = DeviceError::ERROR_NO_ERRORS; + } + + public function set($id, $params) { + $misc = $this->getModule('misc'); + $device_name = $this->Request->contains('device_name') && $misc->isValidName($this->Request['device_name']) ? $this->Request['device_name'] : null; + $drive = $this->Request->contains('drive') && $misc->isValidName($this->Request['drive']) ? $this->Request['drive'] : null; + $slot = $this->Request->contains('slot') && $misc->isValidInteger($this->Request['slot']) ? intval($this->Request['slot']) : null; + + if (is_null($drive)) { + $this->output = DeviceError::MSG_ERROR_DEVICE_AUTOCHANGER_DRIVE_DOES_NOT_EXIST; + $this->error = DeviceError::ERROR_DEVICE_AUTOCHANGER_DRIVE_DOES_NOT_EXIST; + return; + } + + if (is_null($slot)) { + $this->output = DeviceError::MSG_ERROR_DEVICE_WRONG_SLOT_NUMBER; + $this->error = DeviceError::ERROR_DEVICE_WRONG_SLOT_NUMBER; + return; + } + + $result = $this->getModule('changer_command')->execChangerCommand( + $device_name, + 'load', + $drive, + $slot, + null, + ChangerCommand::PTYPE_BG_CMD + ); + $this->output = $result->output; + $this->error = $result->error; + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/ChangerDriveLoaded.php b/gui/baculum/protected/API/Pages/API/ChangerDriveLoaded.php new file mode 100644 index 000000000..25d476081 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/ChangerDriveLoaded.php @@ -0,0 +1,58 @@ + + * @category API + * @package Baculum API + */ +class ChangerDriveLoaded extends BaculumAPIServer { + + public function get() { + $misc = $this->getModule('misc'); + $device_name = $this->Request->contains('device_name') && $misc->isValidName($this->Request['device_name']) ? $this->Request['device_name'] : null; + $drive = $this->Request->contains('drive') && $misc->isValidName($this->Request['drive']) ? $this->Request['drive'] : null; + + if (is_null($drive)) { + $this->output = ChangerCommandError::MSG_ERROR_CHANGER_COMMAND_AUTOCHANGER_DRIVE_DOES_NOT_EXIST; + $this->error = ChangerCommandError::ERROR_CHANGER_COMMAND_AUTOCHANGER_DRIVE_DOES_NOT_EXIST; + return; + } + + $result = $this->getModule('changer_command')->execChangerCommand( + $device_name, + 'loaded', + $drive + ); + + if ($result->error === 0 && count($result->output)) { + $this->output = ['slot' => intval($result->output[0])]; + } else { + $this->output = $result->output; + } + + $this->error = $result->error; + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/ChangerDriveUnload.php b/gui/baculum/protected/API/Pages/API/ChangerDriveUnload.php new file mode 100644 index 000000000..84e9749b6 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/ChangerDriveUnload.php @@ -0,0 +1,75 @@ + + * @category API + * @package Baculum API + */ +class ChangerDriveUnload extends BaculumAPIServer { + + public function get() { + $output = []; + $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 = ChangerCommand::readOutputFile($out_id); + } + $this->output = $output; + $this->error = DeviceError::ERROR_NO_ERRORS; + } + + public function set($id, $params) { + $misc = $this->getModule('misc'); + $device_name = $this->Request->contains('device_name') && $misc->isValidName($this->Request['device_name']) ? $this->Request['device_name'] : null; + $drive = $this->Request->contains('drive') && $misc->isValidName($this->Request['drive']) ? $this->Request['drive'] : null; + $slot = $this->Request->contains('slot') && $misc->isValidInteger($this->Request['slot']) ? intval($this->Request['slot']) : null; + + if (is_null($drive)) { + $this->output = ChangerCommandError::MSG_ERROR_CHANGER_COMMAND_AUTOCHANGER_DRIVE_DOES_NOT_EXIST; + $this->error = ChangerCommandError::ERROR_CHANGER_COMMAND_AUTOCHANGER_DRIVE_DOES_NOT_EXIST; + return; + } + + if (is_null($slot)) { + $this->output = ChangerCommandError::MSG_ERROR_CHANGER_COMMAND_WRONG_SLOT_NUMBER; + $this->error = ChangerCommandError::ERROR_CHANGER_COMMAND_WRONG_SLOT_NUMBER; + return; + } + + $result = $this->getModule('changer_command')->execChangerCommand( + $device_name, + 'unload', + $drive, + $slot, + null, + ChangerCommand::PTYPE_BG_CMD + ); + $this->output = $result->output; + $this->error = $result->error; + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/ChangerList.php b/gui/baculum/protected/API/Pages/API/ChangerList.php new file mode 100644 index 000000000..74c9b9193 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/ChangerList.php @@ -0,0 +1,70 @@ + + * @category API + * @package Baculum API + */ +class ChangerList extends BaculumAPIServer { + + const LIST_PATTERN = '/^(?P\d+):(?P\S+)$/'; + + public function get() { + $misc = $this->getModule('misc'); + $device_name = $this->Request->contains('device_name') && $misc->isValidName($this->Request['device_name']) ? $this->Request['device_name'] : null; + + if (is_null($device_name)) { + $output = DeviceError::MSG_ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST; + $error = DeviceError::ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST; + return; + } + + $result = $this->getModule('changer_command')->execChangerCommand( + $device_name, + 'list' + ); + + if ($result->error === 0) { + $this->output = $this->parseList($result->output); + } else { + $this->output = $result->output; + } + $this->error = $result->error; + } + + private function parseList($output) { + $list = []; + for ($i = 0; $i < count($output); $i++) { + if (preg_match(self::LIST_PATTERN, $output[$i], $match) == 1) { + $list[] = [ + 'slot' => $match['slot'], + 'volume' => $match['volume'] + ]; + } + } + return $list; + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/ChangerListAll.php b/gui/baculum/protected/API/Pages/API/ChangerListAll.php new file mode 100644 index 000000000..876aea3e7 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/ChangerListAll.php @@ -0,0 +1,162 @@ + + * @category API + * @package Baculum API + */ +class ChangerListAll extends BaculumAPIServer { + + const LIST_ALL_DRIVE_PATTERN = '/^D:(?P\d+):(?P[EF]):?(?P\d+)?:?(?P\S+)?$/'; + const LIST_ALL_SLOT_PATTERN = '/^S:(?P\d+):(?P[EF]):?(?P\S+)?$/'; + const LIST_ALL_IO_SLOT_PATTERN = '/^I:(?P\d+):(?P[EF]):?(?P\S+)?$/'; + + public function get() { + $misc = $this->getModule('misc'); + $device_name = $this->Request->contains('device_name') && $misc->isValidName($this->Request['device_name']) ? $this->Request['device_name'] : null; + + if (is_null($device_name)) { + $output = DeviceError::MSG_ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST; + $error = DeviceError::ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST; + return; + } + + $result = $this->getModule('changer_command')->execChangerCommand( + $device_name, + 'listall' + ); + + if ($result->error === 0) { + $this->output = $this->parseListAll($device_name, $result->output); + } else { + $this->output = $result->output; + } + $this->error = $result->error; + } + + private function parseListAll($device_name, $output) { + $list = ['drives' => [], 'slots' => [], 'ie_slots' => []]; + $drives = $this->getModule('device_config')->getChangerDrives($device_name); + $volumes = $this->getModule('volume')->getVolumesKeys(); + $get_volume_info = function($volname) use ($volumes) { + $volume = [ + 'mediaid' => 0, + 'volume' => '', + 'mediatype' => '', + 'pool' => '', + 'lastwritten' => '', + 'whenexpire' => '', + 'volbytes' => '', + 'volstatus' => '', + 'slot' => '' + ]; + if (key_exists($volname, $volumes)) { + $volume['mediaid'] = intval($volumes[$volname]->mediaid); + $volume['mediatype'] = $volumes[$volname]->mediatype; + $volume['pool'] = $volumes[$volname]->pool; + $volume['lastwritten'] = $volumes[$volname]->lastwritten; + $volume['whenexpire'] = $volumes[$volname]->whenexpire; + $volume['volbytes'] = $volumes[$volname]->volbytes; + $volume['volstatus'] = $volumes[$volname]->volstatus; + $volume['slot'] = $volumes[$volname]->slot; + } + return $volume; + }; + for ($i = 0; $i < count($output); $i++) { + if (preg_match(self::LIST_ALL_DRIVE_PATTERN, $output[$i], $match) == 1) { + $index = intval($match['index']); + if (!key_exists($index, $drives)) { + continue; + } + $drive = $drives[$index]['name']; + $device = $drives[$index]['device']; + $volume = ''; + if (key_exists('volume', $match)) { + $volume = $match['volume']; + } + $volinfo = $get_volume_info($volume); + $list['drives'][] = [ + 'type' => 'drive', + 'index' => $index, + 'drive' => $drive, + 'device' => $device, + 'state' => $match['state'], + 'slot_ach' => key_exists('slot', $match) ? intval($match['slot']) : '', + 'mediaid' => $volinfo['mediaid'], + 'volume' => $volume, + 'mediatype' => $volinfo['mediatype'], + 'pool' => $volinfo['pool'], + 'lastwritten' => $volinfo['lastwritten'], + 'whenexpire' => $volinfo['whenexpire'], + 'volbytes' => $volinfo['volbytes'], + 'volstatus' => $volinfo['volstatus'], + 'slot_cat' => $volinfo['slot'] + ]; + } elseif (preg_match(self::LIST_ALL_SLOT_PATTERN, $output[$i], $match) == 1) { + $volume = ''; + if (key_exists('volume', $match)) { + $volume = $match['volume']; + } + $volinfo = $get_volume_info($volume); + $list['slots'][] = [ + 'type' => 'slot', + 'slot_ach' => intval($match['slot']), + 'state' => $match['state'], + 'mediaid' => $volinfo['mediaid'], + 'volume' => $volume, + 'mediatype' => $volinfo['mediatype'], + 'pool' => $volinfo['pool'], + 'lastwritten' => $volinfo['lastwritten'], + 'whenexpire' => $volinfo['whenexpire'], + 'volbytes' => $volinfo['volbytes'], + 'volstatus' => $volinfo['volstatus'], + 'slot_cat' => $volinfo['slot'] + ]; + } elseif (preg_match(self::LIST_ALL_IO_SLOT_PATTERN, $output[$i], $match) == 1) { + $volume = ''; + if (key_exists('volume', $match)) { + $volume = $match['volume']; + } + $volinfo = $get_volume_info($volume); + $list['ie_slots'][] = [ + 'type' => 'ie_slot', + 'slot_ach' => intval($match['slot']), + 'state' => $match['state'], + 'mediaid' => $volinfo['mediaid'], + 'volume' => $volume, + 'mediatype' => $volinfo['mediatype'], + 'pool' => $volinfo['pool'], + 'lastwritten' => $volinfo['lastwritten'], + 'whenexpire' => $volinfo['whenexpire'], + 'volbytes' => $volinfo['volbytes'], + 'volstatus' => $volinfo['volstatus'], + 'slot_cat' => $volinfo['slot'] + ]; + } + } + return $list; + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/ChangerSlots.php b/gui/baculum/protected/API/Pages/API/ChangerSlots.php new file mode 100644 index 000000000..ba741d34f --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/ChangerSlots.php @@ -0,0 +1,49 @@ + + * @category API + * @package Baculum API + */ +class ChangerSlots extends BaculumAPIServer { + + public function get() { + $misc = $this->getModule('misc'); + $device_name = $this->Request->contains('device_name') && $misc->isValidName($this->Request['device_name']) ? $this->Request['device_name'] : null; + + $result = $this->getModule('changer_command')->execChangerCommand( + $device_name, + 'slots' + ); + + if ($result->error === 0 && count($result->output)) { + $this->output = ['slots' => intval($result->output[0])]; + } else { + $this->output = $result->output; + } + $this->error = $result->error; + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/ChangerSlotsTransfer.php b/gui/baculum/protected/API/Pages/API/ChangerSlotsTransfer.php new file mode 100644 index 000000000..5c0d7c6ef --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/ChangerSlotsTransfer.php @@ -0,0 +1,69 @@ + + * @category API + * @package Baculum API + */ +class ChangerSlotsTransfer extends BaculumAPIServer { + + public function get() { + $output = []; + $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 = ChangerCommand::readOutputFile($out_id); + } + $this->output = $output; + $this->error = DeviceError::ERROR_NO_ERRORS; + } + + public function set($id, $params) { + $misc = $this->getModule('misc'); + $device_name = $this->Request->contains('device_name') && $misc->isValidName($this->Request['device_name']) ? $this->Request['device_name'] : null; + $slotsrc = $this->Request->contains('slotsrc') && $misc->isValidInteger($this->Request['slotsrc']) ? intval($this->Request['slotsrc']) : null; + $slotdest = $this->Request->contains('slotdest') && $misc->isValidInteger($this->Request['slotdest']) ? intval($this->Request['slotdest']) : null; + + if (is_null($slotsrc) || is_null($slotdest)) { + $this->output = DeviceError::MSG_ERROR_DEVICE_WRONG_SLOT_NUMBER; + $this->error = DeviceError::ERROR_DEVICE_WRONG_SLOT_NUMBER; + return; + } + + $result = $this->getModule('changer_command')->execChangerCommand( + $device_name, + 'transfer', + null, + $slotsrc, + $slotdest, + ChangerCommand::PTYPE_BG_CMD + ); + $this->output = $result->output; + $this->error = $result->error; + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/StorageMount.php b/gui/baculum/protected/API/Pages/API/StorageMount.php index 489eff055..8a04dc862 100644 --- a/gui/baculum/protected/API/Pages/API/StorageMount.php +++ b/gui/baculum/protected/API/Pages/API/StorageMount.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2021 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -20,6 +20,8 @@ * Bacula(R) is a registered trademark of Kern Sibbald. */ +Prado::using('Application.API.Class.Bconsole'); + /** * Mount storage command endpoint. * @@ -28,28 +30,41 @@ * @package Baculum API */ class StorageMount extends BaculumAPIServer { + public function get() { - $storageid = $this->Request->contains('id') ? intval($this->Request['id']) : 0; + $output = []; + $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 = StorageError::ERROR_NO_ERRORS; + } + + public function set($id, $params) { $drive = $this->Request->contains('drive') ? intval($this->Request['drive']) : 0; $device = ($this->Request->contains('device') && $this->getModule('misc')->isValidName($this->Request['device'])) ? $this->Request['device'] : null; $slot = $this->Request->contains('slot') ? intval($this->Request['slot']) : 0; $result = $this->getModule('bconsole')->bconsoleCommand( $this->director, - array('.storage') + ['.storage'] ); if ($result->exitcode === 0) { array_shift($result->output); - $storage = $this->getModule('storage')->getStorageById($storageid); + $storage = $this->getModule('storage')->getStorageById($id); if (is_object($storage) && in_array($storage->name, $result->output)) { $result = $this->getModule('bconsole')->bconsoleCommand( $this->director, - array( + [ 'mount', 'storage="' . $storage->name . '"', (is_string($device) ? 'device="' . $device . '" drive=0' : 'drive=' . $drive), 'slot=' . $slot - ) + ], + Bconsole::PTYPE_BG_CMD, + true ); $this->output = $result->output; $this->error = $result->exitcode; diff --git a/gui/baculum/protected/API/Pages/API/StorageMountV1.php b/gui/baculum/protected/API/Pages/API/StorageMountV1.php new file mode 100644 index 000000000..f98f3fa26 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/StorageMountV1.php @@ -0,0 +1,67 @@ + + * @category API + * @package Baculum API + */ +class StorageMountV1 extends BaculumAPIServer { + public function get() { + $storageid = $this->Request->contains('id') ? intval($this->Request['id']) : 0; + $drive = $this->Request->contains('drive') ? intval($this->Request['drive']) : 0; + $device = ($this->Request->contains('device') && $this->getModule('misc')->isValidName($this->Request['device'])) ? $this->Request['device'] : null; + $slot = $this->Request->contains('slot') ? intval($this->Request['slot']) : 0; + + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array('.storage') + ); + if ($result->exitcode === 0) { + array_shift($result->output); + $storage = $this->getModule('storage')->getStorageById($storageid); + if (is_object($storage) && in_array($storage->name, $result->output)) { + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array( + 'mount', + 'storage="' . $storage->name . '"', + (is_string($device) ? 'device="' . $device . '" drive=0' : 'drive=' . $drive), + 'slot=' . $slot + ) + ); + $this->output = $result->output; + $this->error = $result->exitcode; + } else { + $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS; + $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS; + } + } else { + $this->output = $result->output; + $this->error = $result->exitcode; + } + } +} + +?> diff --git a/gui/baculum/protected/API/Pages/API/StorageRelease.php b/gui/baculum/protected/API/Pages/API/StorageRelease.php index ce15cdbc8..965aa5f6d 100644 --- a/gui/baculum/protected/API/Pages/API/StorageRelease.php +++ b/gui/baculum/protected/API/Pages/API/StorageRelease.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2021 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -20,6 +20,8 @@ * Bacula(R) is a registered trademark of Kern Sibbald. */ +Prado::using('Application.API.Class.Bconsole'); + /** * Release storage command endpoint. * @@ -28,27 +30,39 @@ * @package Baculum API */ class StorageRelease extends BaculumAPIServer { + public function get() { - $storageid = $this->Request->contains('id') ? intval($this->Request['id']) : 0; + $output = []; + $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 = StorageError::ERROR_NO_ERRORS; + } + + public function set($id, $params) { $drive = $this->Request->contains('drive') ? intval($this->Request['drive']) : 0; $device = $this->Request->contains('device') ? $this->Request['device'] : null; $result = $this->getModule('bconsole')->bconsoleCommand( $this->director, - array('.storage') + ['.storage'] ); - if ($result->exitcode === 0) { array_shift($result->output); - $storage = $this->getModule('storage')->getStorageById($storageid); + $storage = $this->getModule('storage')->getStorageById($id); if (is_object($storage) && in_array($storage->name, $result->output)) { $result = $this->getModule('bconsole')->bconsoleCommand( $this->director, - array( + [ 'release', 'storage="' . $storage->name . '"', (is_string($device) ? 'device="' . $device . '" drive=0 slot=0' : 'drive=' . $drive . ' slot=0') - ) + ], + Bconsole::PTYPE_BG_CMD, + true ); $this->output = $result->output; $this->error = $result->exitcode; diff --git a/gui/baculum/protected/API/Pages/API/StorageReleaseV1.php b/gui/baculum/protected/API/Pages/API/StorageReleaseV1.php new file mode 100644 index 000000000..e319f758a --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/StorageReleaseV1.php @@ -0,0 +1,66 @@ + + * @category API + * @package Baculum API + */ +class StorageReleaseV1 extends BaculumAPIServer { + public function get() { + $storageid = $this->Request->contains('id') ? intval($this->Request['id']) : 0; + $drive = $this->Request->contains('drive') ? intval($this->Request['drive']) : 0; + $device = $this->Request->contains('device') ? $this->Request['device'] : null; + + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array('.storage') + ); + + if ($result->exitcode === 0) { + array_shift($result->output); + $storage = $this->getModule('storage')->getStorageById($storageid); + if (is_object($storage) && in_array($storage->name, $result->output)) { + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array( + 'release', + 'storage="' . $storage->name . '"', + (is_string($device) ? 'device="' . $device . '" drive=0 slot=0' : 'drive=' . $drive . ' slot=0') + ) + ); + $this->output = $result->output; + $this->error = $result->exitcode; + } else { + $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS; + $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS; + } + } else { + $this->output = $result->output; + $this->error = $result->exitcode; + } + } +} + +?> diff --git a/gui/baculum/protected/API/Pages/API/StorageUmount.php b/gui/baculum/protected/API/Pages/API/StorageUmount.php index 657f1fbe8..e89f6a176 100644 --- a/gui/baculum/protected/API/Pages/API/StorageUmount.php +++ b/gui/baculum/protected/API/Pages/API/StorageUmount.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2021 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -19,6 +19,8 @@ * * Bacula(R) is a registered trademark of Kern Sibbald. */ + +Prado::using('Application.API.Class.Bconsole'); /** * Storage umount command endpoint. @@ -28,26 +30,39 @@ * @package Baculum API */ class StorageUmount extends BaculumAPIServer { + public function get() { - $storageid = $this->Request->contains('id') ? intval($this->Request['id']) : 0; + $output = []; + $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 = StorageError::ERROR_NO_ERRORS; + } + + public function set($id, $params) { $drive = $this->Request->contains('drive') ? intval($this->Request['drive']) : 0; $device = $this->Request->contains('device') ? $this->Request['device'] : null; $result = $this->getModule('bconsole')->bconsoleCommand( $this->director, - array('.storage') + ['.storage'] ); if ($result->exitcode === 0) { array_shift($result->output); - $storage = $this->getModule('storage')->getStorageById($storageid); + $storage = $this->getModule('storage')->getStorageById($id); if (is_object($storage) && in_array($storage->name, $result->output)) { $result = $this->getModule('bconsole')->bconsoleCommand( $this->director, - array( + [ 'umount', 'storage="' . $storage->name . '"', (is_string($device) ? 'device="' . $device . '" slot=0 drive=0' : 'drive=' . $drive . ' slot=0') - ) + ], + Bconsole::PTYPE_BG_CMD, + true ); $this->output = $result->output; $this->error = $result->exitcode; diff --git a/gui/baculum/protected/API/Pages/API/StorageUmountV1.php b/gui/baculum/protected/API/Pages/API/StorageUmountV1.php new file mode 100644 index 000000000..705094b64 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/StorageUmountV1.php @@ -0,0 +1,65 @@ + + * @category API + * @package Baculum API + */ +class StorageUmountV1 extends BaculumAPIServer { + public function get() { + $storageid = $this->Request->contains('id') ? intval($this->Request['id']) : 0; + $drive = $this->Request->contains('drive') ? intval($this->Request['drive']) : 0; + $device = $this->Request->contains('device') ? $this->Request['device'] : null; + + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array('.storage') + ); + if ($result->exitcode === 0) { + array_shift($result->output); + $storage = $this->getModule('storage')->getStorageById($storageid); + if (is_object($storage) && in_array($storage->name, $result->output)) { + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array( + 'umount', + 'storage="' . $storage->name . '"', + (is_string($device) ? 'device="' . $device . '" slot=0 drive=0' : 'drive=' . $drive . ' slot=0') + ) + ); + $this->output = $result->output; + $this->error = $result->exitcode; + } else { + $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS; + $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS; + } + } else { + $this->output = $result->output; + $this->error = $result->exitcode; + } + } +} + +?> diff --git a/gui/baculum/protected/API/Pages/API/VolumeLabel.php b/gui/baculum/protected/API/Pages/API/VolumeLabel.php index 5a943057b..988f47f8a 100644 --- a/gui/baculum/protected/API/Pages/API/VolumeLabel.php +++ b/gui/baculum/protected/API/Pages/API/VolumeLabel.php @@ -49,14 +49,14 @@ class VolumeLabel extends BaculumAPIServer { $misc = $this->getModule('misc'); if (!$misc->isValidName($volume)) { - $this->output = VolumeError::ERROR_INVALID_VOLUME; - $this->error = VolumeError::MSG_ERROR_INVALID_VOLUME; + $this->output = VolumeError::MSG_ERROR_INVALID_VOLUME; + $this->error = VolumeError::ERROR_INVALID_VOLUME; return; } if (!$misc->isValidInteger($slot)) { - $this->output = VolumeError::ERROR_INVALID_SLOT; - $this->error = VolumeError::MSG_ERROR_INVALID_SLOT; + $this->output = VolumeError::MSG_ERROR_INVALID_SLOT; + $this->error = VolumeError::ERROR_INVALID_SLOT; return; } diff --git a/gui/baculum/protected/API/Pages/API/config.xml b/gui/baculum/protected/API/Pages/API/config.xml index 6381b73f5..568c92f61 100644 --- a/gui/baculum/protected/API/Pages/API/config.xml +++ b/gui/baculum/protected/API/Pages/API/config.xml @@ -33,6 +33,7 @@ + @@ -46,5 +47,7 @@ + + diff --git a/gui/baculum/protected/API/Pages/API/endpoints.xml b/gui/baculum/protected/API/Pages/API/endpoints.xml index bccbc1e19..5f3bbc433 100644 --- a/gui/baculum/protected/API/Pages/API/endpoints.xml +++ b/gui/baculum/protected/API/Pages/API/endpoints.xml @@ -34,6 +34,14 @@ + + + + + + + + @@ -126,9 +134,9 @@ - - - + + + diff --git a/gui/baculum/protected/API/Pages/Panel/APIDevices.page b/gui/baculum/protected/API/Pages/Panel/APIDevices.page new file mode 100644 index 000000000..eb1d4e3a2 --- /dev/null +++ b/gui/baculum/protected/API/Pages/Panel/APIDevices.page @@ -0,0 +1,866 @@ +<%@ MasterClass="Application.API.Layouts.Main" Theme="Baculum-v2"%> + +
+
+ <%[ Devices ]%> +
+
+
+ + +
+
+ +  <%[ Add autochanger ]%> + + + + + + + + + + + + + + + + + + + +
<%[ Name ]%><%[ Device ]%><%[ Actions ]%>
<%[ Name ]%><%[ Device ]%><%[ Actions ]%>
+
+ + +
+
+
+ × + + +
+
+ +
+
+
+ +
+
+
+
+
+ + + +
  +
+
+
+
+ + +
  +
+
+
+
+ + +
  +
+ +
+
+
+
+ + + + + +
+ + + $('#changer_command_test_result_ok').hide(); + $('#changer_command_test_result_err').hide(); + $('#<%=$this->ChangerCommandTestResultErr->ClientID%>').hide(); + $('#changer_command_test_loader').show(); + + + $('#changer_command_test_loader').hide(); + +  <%[ test ]%> + + + + + + <%[ Changer command error ]%> +
+
+
+
+
+
+ + +

<%[ Use CTRL + left-click to multiple item selection ]%>

+
  +
+
+ + +  <%[ Save ]%> + +
+ +
+
+
+ + + + + + + +
+
+
+ × + + +
+
+ +
+
+
+ +
+
+
+
+
+ + + +
  +
+
+
+
+ + +
  +
+
+
+
+ + +
  +
+
+ + +  <%[ Save ]%> + +
+ +
+
+
+ + + + +
diff --git a/gui/baculum/protected/API/Pages/Panel/APIDevices.php b/gui/baculum/protected/API/Pages/Panel/APIDevices.php new file mode 100644 index 000000000..895850209 --- /dev/null +++ b/gui/baculum/protected/API/Pages/Panel/APIDevices.php @@ -0,0 +1,321 @@ + + * @category Panel + * @package Baculum API + */ +class APIDevices extends BaculumAPIPage { + + const WINDOW_TYPE_ADD = 'add'; + const WINDOW_TYPE_EDIT = 'edit'; + + private $config; + + public function onInit($param) { + parent::onInit($param); + $this->config = $this->getModule('device_config')->getConfig(); + } + + public function setAutochangerList($sender, $param) { + $devices = []; + foreach ($this->config as $name => $device) { + if ($device['type'] !== DeviceConfig::DEV_TYPE_AUTOCHANGER) { + continue; + } + $device['name'] = $name; + $devices[] = $device; + } + + $this->getCallbackClient()->callClientFunction( + 'oAPIAutochangers.load_autochanger_list_cb', + [$devices] + ); + + if (is_object($sender)) { + $this->setConfigAutochangers(); + } + } + + public function addAutochanger($sender, $param) { + $ach_drives = $this->getAutochangerDrives(); + $disabled_indices = []; + for ($i = 0; $i < $this->ChangerDevices->getItemCount(); $i++) { + $item = $this->ChangerDevices->Items[$i]; + if (key_exists($item->Value, $ach_drives)) { + $disabled_indices[] = $i; + } + } + + $this->getCallbackClient()->callClientFunction( + 'oAPIAutochangers.set_disabled_drives', + [$disabled_indices] + ); + } + + public function loadAutochanger($sender, $param) { + $ach_name = $param->getCallbackParameter(); + $ach = []; + foreach ($this->config as $name => $device) { + if ($device['type'] !== DeviceConfig::DEV_TYPE_AUTOCHANGER) { + continue; + } + if ($name == $ach_name) { + $ach = $device; + break; + } + } + if (count($ach) > 0) { + $this->AutochangerName->Text = $name; + $this->ChangerDevice->Text = $ach['device']; + $this->ChangerCommand->Text = $ach['command']; + $this->ChangerCommandUseSudo->Checked = ($ach['use_sudo'] == 1); + $drives = explode(',', $ach['drives']); + $ach_drives = $this->getAutochangerDrives(); + $disabled_indices = []; + $selected_indices = []; + for ($i = 0; $i < $this->ChangerDevices->getItemCount(); $i++) { + $item = $this->ChangerDevices->Items[$i]; + if (key_exists($item->Value, $ach_drives) && $ach_drives[$item->Value] !== $name) { + $disabled_indices[] = $i; + continue; + } + if (in_array($item->Value, $drives)) { + $selected_indices[] = $i; + } + } + $this->ChangerDevices->setSelectedIndices($selected_indices); + $this->getCallbackClient()->callClientFunction( + 'oAPIAutochangers.set_disabled_drives', + [$disabled_indices] + ); + } + } + + public function saveAutochanger($sender, $param) { + if ($this->AutochangerWindowType->Value == self::WINDOW_TYPE_ADD && key_exists($this->AutochangerName->Text, $this->config)) { + $this->getCallbackClient()->show('autochanger_exists'); + return; + } + $drives = []; + $selected_indices = $this->ChangerDevices->getSelectedIndices(); + foreach ($selected_indices as $indice) { + for ($i = 0; $i < $this->ChangerDevices->getItemCount(); $i++) { + if ($i === $indice) { + $drives[] = $this->ChangerDevices->Items[$i]->Value; + } + } + } + $autochanger = [ + 'type' => DeviceConfig::DEV_TYPE_AUTOCHANGER, + 'device' => $this->ChangerDevice->Text, + 'command' => $this->ChangerCommand->Text, + 'use_sudo' => $this->ChangerCommandUseSudo->Checked ? '1' : '0', + 'drives'=> implode(',', $drives) + ]; + $this->config[$this->AutochangerName->Text] = $autochanger; + $result = $this->getModule('device_config')->setConfig($this->config); + if ($result) { + $this->getCallbackClient()->callClientFunction('oAPIAutochangers.show_autochanger_window', [false]); + $this->setAutochangerList(null, null); + $this->setDeviceList(null, null); + } + } + + public function deleteAutochanger($sender, $param) { + $ach = $param->getCallbackParameter(); + if (!key_exists($ach, $this->config)) { + return; + } + unset($this->config[$ach]); + $result = $this->getModule('device_config')->setConfig($this->config); + if ($result) { + $this->setAutochangerList(null, null); + $this->setDeviceList(null, null); + } + } + + public function testChangerCommand($sender, $param) { + $emsg = ''; + $use_sudo = $this->ChangerCommandUseSudo->Checked; + $changer_command = $this->ChangerCommand->Text; + $changer_device = $this->ChangerDevice->Text; + $command = 'listall'; + // slot, archive device and index are not used in listall cmd + $slot = 0; + $archive_device = '/dev/null'; + $drive_index = 0; + $is_validate = false; + if (!empty($changer_command) && !empty($changer_device)) { + $result = $this->getModule('changer_command')->testChangerCommand( + $use_sudo, + $changer_command, + $changer_device, + $command, + $slot, + $archive_device, + $drive_index + ); + $is_validate = ($result->error === 0); + if (!$is_validate) { + $this->ChangerCommandTestResultErr->Text = implode(PHP_EOL, $result->output); + } + } + if ($is_validate === true) { + $this->getCallbackClient()->show('changer_command_test_result_ok'); + $this->getCallbackClient()->hide('changer_command_test_result_err'); + $this->getCallbackClient()->hide($this->ChangerCommandTestResultErr); + } else { + $this->getCallbackClient()->hide('changer_command_test_result_ok'); + $this->getCallbackClient()->show('changer_command_test_result_err'); + $this->getCallbackClient()->show($this->ChangerCommandTestResultErr); + } + } + + public function setDeviceList($sender, $param) { + $devices = []; + $ach_devices = $this->getAutochangerDrives(); + $dev_names = []; + foreach ($this->config as $name => $device) { + if ($device['type'] !== DeviceConfig::DEV_TYPE_DEVICE) { + continue; + } + $device['name'] = $name; + $device['autochanger'] = (key_exists($name, $ach_devices)) ? $ach_devices[$name] : ''; + $dev_names[] = $name; + $devices[] = $device; + } + + // Set changer device select list + $this->ChangerDevices->DataSource = array_combine($dev_names, $dev_names); + $this->ChangerDevices->dataBind(); + + $this->getCallbackClient()->callClientFunction( + 'oAPIDevices.load_device_list_cb', + [$devices] + ); + + if (is_object($sender)) { + $this->setConfigDevices(); + } + } + + private function getAutochangerDrives() { + $ach_devices = []; + foreach ($this->config as $name => $device) { + if ($device['type'] !== DeviceConfig::DEV_TYPE_AUTOCHANGER) { + continue; + } + $drives = explode(',', $device['drives']); + for ($i = 0; $i < count($drives); $i++) { + $ach_devices[$drives[$i]] = $name; + } + } + return $ach_devices; + } + + public function loadDevice($sender, $param) { + $dev_name = $param->getCallbackParameter(); + $dev = []; + foreach ($this->config as $name => $device) { + if ($device['type'] !== DeviceConfig::DEV_TYPE_DEVICE) { + continue; + } + if ($name == $dev_name) { + $dev = $device; + break; + } + } + if (count($dev) > 0) { + $this->DeviceName->Text = $name; + $this->DeviceDevice->Text = $dev['device']; + $this->DeviceIndex->Text = $dev['index']; + } + } + + public function saveDevice($sender, $param) { + if ($this->DeviceWindowType->Value == self::WINDOW_TYPE_ADD && key_exists($this->DeviceName->Text, $this->config)) { + $this->getCallbackClient()->show('device_exists'); + return; + } + $device = [ + 'type' => DeviceConfig::DEV_TYPE_DEVICE, + 'device' => $this->DeviceDevice->Text, + 'index' => intval($this->DeviceIndex->Text) + ]; + $this->config[$this->DeviceName->Text] = $device; + $result = $this->getModule('device_config')->setConfig($this->config); + if ($result) { + $this->getCallbackClient()->callClientFunction( + 'oAPIDevices.show_device_window', + [false] + ); + $this->setDeviceList(null, null); + } + } + + public function deleteDevice($sender, $param) { + $device = $param->getCallbackParameter(); + if (!key_exists($device, $this->config)) { + return; + } + unset($this->config[$device]); + $result = $this->getModule('device_config')->setConfig($this->config); + if ($result) { + $this->setDeviceList(null, null); + } + } + + private function setConfigAutochangers() { + $achs = []; + try { + $achs = $this->getModule('bacula_setting')->getConfig('sd', 'Autochanger'); + } catch (BConfigException $e) { + // do nothing + } + $this->getCallbackClient()->callClientFunction( + 'oAPIAutochangers.set_config_autochangers', + [$achs] + ); + } + + private function setConfigDevices() { + $devs = []; + try { + $devs = $this->getModule('bacula_setting')->getConfig('sd', 'Device'); + } catch (BConfigException $e) { + // do nothing + } + $this->getCallbackClient()->callClientFunction( + 'oAPIDevices.set_config_devices', + [$devs] + ); + } +} +?> diff --git a/gui/baculum/protected/API/Pages/Panel/APIInstallWizard.page b/gui/baculum/protected/API/Pages/Panel/APIInstallWizard.page index 8ef6fc3d4..0c49cd0fa 100644 --- a/gui/baculum/protected/API/Pages/Panel/APIInstallWizard.page +++ b/gui/baculum/protected/API/Pages/Panel/APIInstallWizard.page @@ -401,7 +401,7 @@  <%[ Get sudo configuration ]%> + />  <%[ Get sudo configuration ]%> @@ -534,7 +534,7 @@  <%[ Get sudo configuration ]%> + />  <%[ Get sudo configuration ]%> @@ -1248,22 +1248,7 @@ - -

<%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%>

-

<%[ Note ]%> <%[ Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file. ]%> -

<%[ Example sudo configuration for Apache web server user (RHEL, CentOS and others): ]%>

-

-	

<%[ Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others): ]%>

-

-	

<%[ Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others): ]%>

-

-
+ diff --git a/gui/baculum/protected/API/Pages/Panel/APISettings.page b/gui/baculum/protected/API/Pages/Panel/APISettings.page index e4dc9cfdb..5c9380b2e 100644 --- a/gui/baculum/protected/API/Pages/Panel/APISettings.page +++ b/gui/baculum/protected/API/Pages/Panel/APISettings.page @@ -312,7 +312,7 @@  <%[ Get sudo configuration ]%> + />  <%[ Get sudo configuration ]%> @@ -772,7 +772,7 @@  <%[ Get sudo configuration ]%> + />  <%[ Get sudo configuration ]%> @@ -1232,62 +1232,31 @@ - -

<%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%>

-

<%[ Note ]%> <%[ Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file. ]%> -

<%[ Example sudo configuration for Apache web server user (RHEL, CentOS and others): ]%>

-

-	

<%[ Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others): ]%>

-

-	

<%[ Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others): ]%>

-

-
+ + +

<%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%>

+

<%[ Note ]%> <%[ Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file. ]%> +

<%[ Example sudo configuration for Apache web server user (RHEL, CentOS and others): ]%>

+

+	

<%[ Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others): ]%>

+

+	

<%[ Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others): ]%>

+

+
diff --git a/gui/baculum/protected/Common/Class/BClientScript.php b/gui/baculum/protected/Common/Class/BClientScript.php index dc667c763..ce92ed0d5 100644 --- a/gui/baculum/protected/Common/Class/BClientScript.php +++ b/gui/baculum/protected/Common/Class/BClientScript.php @@ -31,7 +31,7 @@ Prado::using('System.Web.UI.WebControls.TClientScript'); */ class BClientScript extends TClientScript { - const SCRIPTS_VERSION = 17; + const SCRIPTS_VERSION = 18; public function getScriptUrl() { diff --git a/gui/baculum/protected/Common/Class/Errors.php b/gui/baculum/protected/Common/Class/Errors.php index 828daa6c9..2ac2c2bf2 100644 --- a/gui/baculum/protected/Common/Class/Errors.php +++ b/gui/baculum/protected/Common/Class/Errors.php @@ -230,4 +230,21 @@ class OAuth2Error extends GenericError { const MSG_ERROR_OAUTH2_CLIENT_INVALID_CONSOLE = 'Invalid Console name.'; const MSG_ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR = 'Invalid Director name.'; } +class DeviceError extends GenericError { + + const ERROR_DEVICE_DEVICE_CONFIG_DOES_NOT_EXIST = 130; + const ERROR_DEVICE_INVALID_COMMAND = 131; + const ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST = 132; + const ERROR_DEVICE_AUTOCHANGER_DRIVE_DOES_NOT_EXIST = 132; + const ERROR_DEVICE_WRONG_SLOT_NUMBER = 133; + const ERROR_DEVICE_DRIVE_DOES_NOT_BELONG_TO_AUTOCHANGER = 134; + + const MSG_ERROR_DEVICE_DEVICE_CONFIG_DOES_NOT_EXIST = 'Device config does not exist.'; + const MSG_ERROR_DEVICE_INVALID_COMMAND = 'Invalid changer command.'; + const MSG_ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST = 'Autochanger does not exist.'; + const MSG_ERROR_DEVICE_AUTOCHANGER_DRIVE_DOES_NOT_EXIST = 'Autochanger drive does not exist.'; + const MSG_ERROR_DEVICE_WRONG_SLOT_NUMBER = 'Wrong slot number.'; + const MSG_ERROR_DEVICE_DRIVE_DOES_NOT_BELONG_TO_AUTOCHANGER = 'Drive does not belong to selected autochanger.'; +} + ?> diff --git a/gui/baculum/protected/Common/Class/OAuth2.php b/gui/baculum/protected/Common/Class/OAuth2.php index 364c9cefc..485b39403 100644 --- a/gui/baculum/protected/Common/Class/OAuth2.php +++ b/gui/baculum/protected/Common/Class/OAuth2.php @@ -84,10 +84,10 @@ abstract class OAuth2 extends CommonModule { /** * Expiration time in seconds for access token. * - * Temportary set to 60 seconds for testst purposes. + * Temportary set to 15 minutes for testst purposes. * In production the value SHOULD BE changed. */ - const ACCESS_TOKEN_EXPIRES_TIME = 60; + const ACCESS_TOKEN_EXPIRES_TIME = 90000; /** * Scope pattern. diff --git a/gui/baculum/protected/Common/JavaScript/misc.js b/gui/baculum/protected/Common/JavaScript/misc.js index 73f9ae38d..252443cfb 100644 --- a/gui/baculum/protected/Common/JavaScript/misc.js +++ b/gui/baculum/protected/Common/JavaScript/misc.js @@ -206,6 +206,7 @@ var OAuth2Scopes = [ 'directors', 'clients', 'storages', + 'devices', 'volumes', 'pools', 'bvfs', @@ -230,6 +231,17 @@ function copy_to_clipboard(text) { document.body.removeChild(input); } +/** + * Used to escape values before putting them into regular expression. + * Dedicated to use in table values. + */ +dtEscapeRegex = function(value) { + if (typeof(value) != 'string' && typeof(value.toString) == 'function') { + value = value.toString(); + } + return $.fn.dataTable.util.escapeRegex(value); +}; + $(function() { W3SideBar.init(); set_global_listeners(); diff --git a/gui/baculum/protected/Web/JavaScript/misc.js b/gui/baculum/protected/Web/JavaScript/misc.js index 1cb04d4b3..907b866d8 100644 --- a/gui/baculum/protected/Web/JavaScript/misc.js +++ b/gui/baculum/protected/Web/JavaScript/misc.js @@ -343,7 +343,11 @@ function render_jobstatus(data, type, row) { function render_bytes(data, type, row) { var s; if (type == 'display') { - s = Units.get_formatted_size(data) + if (/^\d+$/.test(data)) { + s = Units.get_formatted_size(data); + } else { + s = ''; + } } else { s = data; } @@ -1105,17 +1109,6 @@ function update_job_table(table_obj, new_data) { table_obj.page(current_page).draw(false); } -/** - * Used to escape values before putting them into regular expression. - * Dedicated to use in table values. - */ -dtEscapeRegex = function(value) { - if (typeof(value) != 'string' && typeof(value.toString) == 'function') { - value = value.toString(); - } - return $.fn.dataTable.util.escapeRegex(value); -}; - /** * Do validation comma separated list basing on regular expression * for particular values. @@ -1184,9 +1177,11 @@ function get_table_toolbar(table, actions, txt) { acts[select.value].before(); } selected = selected.join('|'); - acts[select.value].callback.options.RequestTimeOut = 60000; // Timeout set to 1 minute - acts[select.value].callback.setCallbackParameter(selected); - acts[select.value].callback.dispatch(); + if (acts[select.value].hasOwnProperty('callback')) { + acts[select.value].callback.options.RequestTimeOut = 60000; // Timeout set to 1 minute + acts[select.value].callback.setCallbackParameter(selected); + acts[select.value].callback.dispatch(); + } }); table_toolbar.appendChild(title); table_toolbar.appendChild(select); diff --git a/gui/baculum/protected/Web/Lang/en/messages.mo b/gui/baculum/protected/Web/Lang/en/messages.mo index cd34cb87e..44a01c253 100644 Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/en/messages.po b/gui/baculum/protected/Web/Lang/en/messages.po index ad856d8de..71883eadb 100644 --- a/gui/baculum/protected/Web/Lang/en/messages.po +++ b/gui/baculum/protected/Web/Lang/en/messages.po @@ -3208,3 +3208,96 @@ msgstr "Last %errors/%count jobs finished with error." msgid "All last %count jobs finished successfully." msgstr "All last %count jobs finished successfully." + +msgid "Drive index" +msgstr "Drive index" + +msgid "Drive name" +msgstr "Drive name" + +msgid "Unload" +msgstr "Unload" + +msgid "Manage autochanger" +msgstr "Manage autochanger" + +msgid "Slot in device" +msgstr "Slot in device" + +msgid "Slot in catalog" +msgstr "Slot in catalog" + +msgid "Load" +msgstr "Load" + +msgid "Load drive" +msgstr "Load drive" + +msgid "index" +msgstr "index" + +msgid "device" +msgstr "device" + +msgid "Drive" +msgstr "Drive" + +msgid "Empty" +msgstr "Empty" + +msgid "In drive %index (%drive)" +msgstr "In drive %index (%drive)" + +msgid "Import/Export" +msgstr "Import/Export" + +msgid "Label using barcodes" +msgstr "Label using barcodes" + +msgid "Move to import/export slot" +msgstr "Move to import/export slot" + +msgid "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." +msgstr "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." + +msgid "Drive index:" +msgstr "Drive index:" + +msgid "Slots" +msgstr "Slots" + +msgid "Release I/E" +msgstr "Release I/E" + +msgid "Move to import/export slots" +msgstr "Move to import/export slots" + +msgid "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." +msgstr "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." + +msgid "Import/export slot" +msgstr "Import/export slot" + +msgid "Destination slot" +msgstr "Destination slot" + +msgid "Release import/export slot" +msgstr "Release import/export slot" + +msgid "Tape drives" +msgstr "Tape drives" + +msgid "Changer slots" +msgstr "Changer slots" + +msgid "Release all I/E slots" +msgstr "Release all I/E slots" + +msgid "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." +msgstr "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." + +msgid "Tip: To use bulk autochanger actions, please select table rows." +msgstr "Tip: To use bulk autochanger actions, please select table rows." + +msgid "Mount volume" +msgstr "Mount volume" diff --git a/gui/baculum/protected/Web/Lang/ja/messages.mo b/gui/baculum/protected/Web/Lang/ja/messages.mo index fa72c70d8..a039df50c 100644 Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/ja/messages.po b/gui/baculum/protected/Web/Lang/ja/messages.po index f69f6561e..3893d5868 100644 --- a/gui/baculum/protected/Web/Lang/ja/messages.po +++ b/gui/baculum/protected/Web/Lang/ja/messages.po @@ -3294,3 +3294,96 @@ msgstr "Last %errors/%count jobs finished with error." msgid "All last %count jobs finished successfully." msgstr "All last %count jobs finished successfully." + +msgid "Drive index" +msgstr "Drive index" + +msgid "Drive name" +msgstr "Drive name" + +msgid "Unload" +msgstr "Unload" + +msgid "Manage autochanger" +msgstr "Manage autochanger" + +msgid "Slot in device" +msgstr "Slot in device" + +msgid "Slot in catalog" +msgstr "Slot in catalog" + +msgid "Load" +msgstr "Load" + +msgid "Load drive" +msgstr "Load drive" + +msgid "index" +msgstr "index" + +msgid "device" +msgstr "device" + +msgid "Drive" +msgstr "Drive" + +msgid "Empty" +msgstr "Empty" + +msgid "In drive %index (%drive)" +msgstr "In drive %index (%drive)" + +msgid "Import/Export" +msgstr "Import/Export" + +msgid "Label using barcodes" +msgstr "Label using barcodes" + +msgid "Move to import/export slot" +msgstr "Move to import/export slot" + +msgid "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." +msgstr "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." + +msgid "Drive index:" +msgstr "Drive index:" + +msgid "Slots" +msgstr "Slots" + +msgid "Release I/E" +msgstr "Release I/E" + +msgid "Move to import/export slots" +msgstr "Move to import/export slots" + +msgid "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." +msgstr "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." + +msgid "Import/export slot" +msgstr "Import/export slot" + +msgid "Destination slot" +msgstr "Destination slot" + +msgid "Release import/export slot" +msgstr "Release import/export slot" + +msgid "Tape drives" +msgstr "Tape drives" + +msgid "Changer slots" +msgstr "Changer slots" + +msgid "Release all I/E slots" +msgstr "Release all I/E slots" + +msgid "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." +msgstr "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." + +msgid "Tip: To use bulk autochanger actions, please select table rows." +msgstr "Tip: To use bulk autochanger actions, please select table rows." + +msgid "Mount volume" +msgstr "Mount volume" diff --git a/gui/baculum/protected/Web/Lang/pl/messages.mo b/gui/baculum/protected/Web/Lang/pl/messages.mo index 3ff773a62..3ae9096ce 100644 Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pl/messages.po b/gui/baculum/protected/Web/Lang/pl/messages.po index 4553201b0..64735fd31 100644 --- a/gui/baculum/protected/Web/Lang/pl/messages.po +++ b/gui/baculum/protected/Web/Lang/pl/messages.po @@ -3219,3 +3219,95 @@ msgstr "Ostatnie %errors/%count zadań zakończyło się błędem." msgid "All last %count jobs finished successfully." msgstr "Wszystkie ostatnie %count zadań zakończyło się pomyślnie." +msgid "Drive index" +msgstr "Indeks napędu" + +msgid "Drive name" +msgstr "Nazwa napędu" + +msgid "Unload" +msgstr "Wyładuj" + +msgid "Manage autochanger" +msgstr "Zarządzaj zmieniarką taśm" + +msgid "Slot in device" +msgstr "Slot w urządzeniu" + +msgid "Slot in catalog" +msgstr "Slot w bazie danych" + +msgid "Load" +msgstr "Załaduj" + +msgid "Load drive" +msgstr "Załaduj napęd" + +msgid "index" +msgstr "indeks" + +msgid "device" +msgstr "urządzenie" + +msgid "Drive" +msgstr "Napęd" + +msgid "Empty" +msgstr "Pusty" + +msgid "In drive %index (%drive)" +msgstr "W napędzie %index (%drive)" + +msgid "Import/Export" +msgstr "Import/Eksport" + +msgid "Label using barcodes" +msgstr "Etykietuj używając kodów kreskowych" + +msgid "Move to import/export slot" +msgstr "Przenieś do slotu import/eksport" + +msgid "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." +msgstr "Zarządzanie zmieniarką taśm jest niedostępne. W celu zarządzania zmieniarką taśm z tego miejsce, dodaj ją do urządzeń hosta API po stronie hosta API." + +msgid "Drive index:" +msgstr "Indeks napędu:" + +msgid "Slots" +msgstr "Sloty" + +msgid "Release I/E" +msgstr "Zwolnij I/E" + +msgid "Move to import/export slots" +msgstr "Przenieś do slotów import/eksport" + +msgid "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." +msgstr "Jest za mało slotów import/eksport do przetrasferowania wybranych wolumenów. Ilość wolnych slotów import/eksport: %slots_count, ilość wybranych wolumenów: %vols_count." + +msgid "Import/export slot" +msgstr "Sloty import/eksport" + +msgid "Destination slot" +msgstr "Docelowy slot" + +msgid "Release import/export slot" +msgstr "Zwolnij slot import/eksport" + +msgid "Tape drives" +msgstr "Napędy taśmowe" + +msgid "Changer slots" +msgstr "Sloty zmieniarki" + +msgid "Release all I/E slots" +msgstr "Zwolnij wszystkie sloty I/E" + +msgid "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." +msgstr "Jest za mało regularnych slotów do przetrasferowania wybranych wolumenów ze slotów import/eksport. Ilość pełnych slotów import/eksport: %ie_slot_count, ilość wolnych regularnych slotów: %slot_count." + +msgid "Tip: To use bulk autochanger actions, please select table rows." +msgstr "Wskazówka: W celu użycia akcji zbiorczych zmieniarki taśm, proszę zaznaczyć wiersze tabeli." + +msgid "Mount volume" +msgstr "Montuj wolumen" diff --git a/gui/baculum/protected/Web/Lang/pt/messages.mo b/gui/baculum/protected/Web/Lang/pt/messages.mo index f5894b7f7..aacd488f7 100644 Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pt/messages.po b/gui/baculum/protected/Web/Lang/pt/messages.po index 057f5c787..f9948d796 100644 --- a/gui/baculum/protected/Web/Lang/pt/messages.po +++ b/gui/baculum/protected/Web/Lang/pt/messages.po @@ -3219,3 +3219,95 @@ msgstr "Últimos %errors/%count jobs concluídos com erro." msgid "All last %count jobs finished successfully." msgstr "Todos os últimos %count jobs foram concluídos com sucesso." +msgid "Drive index" +msgstr "Drive index" + +msgid "Drive name" +msgstr "Drive name" + +msgid "Unload" +msgstr "Unload" + +msgid "Manage autochanger" +msgstr "Manage autochanger" + +msgid "Slot in device" +msgstr "Slot in device" + +msgid "Slot in catalog" +msgstr "Slot in catalog" + +msgid "Load" +msgstr "Load" + +msgid "Load drive" +msgstr "Load drive" + +msgid "index" +msgstr "index" + +msgid "device" +msgstr "device" + +msgid "Drive" +msgstr "Drive" + +msgid "Empty" +msgstr "Empty" + +msgid "In drive %index (%drive)" +msgstr "In drive %index (%drive)" + +msgid "Import/Export" +msgstr "Import/Export" + +msgid "Label using barcodes" +msgstr "Label using barcodes" + +msgid "Move to import/export slot" +msgstr "Move to import/export slot" + +msgid "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." +msgstr "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." + +msgid "Drive index:" +msgstr "Drive index:" + +msgid "Slots" +msgstr "Slots" + +msgid "Release I/E" +msgstr "Release I/E" + +msgid "Move to import/export slots" +msgstr "Move to import/export slots" + +msgid "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." +msgstr "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." + +msgid "Import/export slot" +msgstr "Import/export slot" + +msgid "Destination slot" +msgstr "Destination slot" + +msgid "Release import/export slot" +msgstr "Release import/export slot" + +msgid "Tape drives" +msgstr "Tape drives" + +msgid "Changer slots" +msgstr "Changer slots" + +msgid "Release all I/E slots" +msgstr "Release all I/E slots" + +msgid "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." +msgstr "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." + +msgid "Tip: To use bulk autochanger actions, please select table rows." +msgstr "Tip: To use bulk autochanger actions, please select table rows." + +msgid "Mount volume" +msgstr "Mount volume" diff --git a/gui/baculum/protected/Web/Lang/ru/messages.mo b/gui/baculum/protected/Web/Lang/ru/messages.mo index ae003a817..7f7d5d2ef 100644 Binary files a/gui/baculum/protected/Web/Lang/ru/messages.mo and b/gui/baculum/protected/Web/Lang/ru/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/ru/messages.po b/gui/baculum/protected/Web/Lang/ru/messages.po index 5006c0da8..0b6922409 100644 --- a/gui/baculum/protected/Web/Lang/ru/messages.po +++ b/gui/baculum/protected/Web/Lang/ru/messages.po @@ -3219,3 +3219,95 @@ msgstr "Последнее %errors/%count заданий закончились msgid "All last %count jobs finished successfully." msgstr "Все последние %count задания успешно завершены." +msgid "Drive index" +msgstr "Drive index" + +msgid "Drive name" +msgstr "Drive name" + +msgid "Unload" +msgstr "Unload" + +msgid "Manage autochanger" +msgstr "Manage autochanger" + +msgid "Slot in device" +msgstr "Slot in device" + +msgid "Slot in catalog" +msgstr "Slot in catalog" + +msgid "Load" +msgstr "Load" + +msgid "Load drive" +msgstr "Load drive" + +msgid "index" +msgstr "index" + +msgid "device" +msgstr "device" + +msgid "Drive" +msgstr "Drive" + +msgid "Empty" +msgstr "Empty" + +msgid "In drive %index (%drive)" +msgstr "In drive %index (%drive)" + +msgid "Import/Export" +msgstr "Import/Export" + +msgid "Label using barcodes" +msgstr "Label using barcodes" + +msgid "Move to import/export slot" +msgstr "Move to import/export slot" + +msgid "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." +msgstr "Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side." + +msgid "Drive index:" +msgstr "Drive index:" + +msgid "Slots" +msgstr "Slots" + +msgid "Release I/E" +msgstr "Release I/E" + +msgid "Move to import/export slots" +msgstr "Move to import/export slots" + +msgid "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." +msgstr "There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count." + +msgid "Import/export slot" +msgstr "Import/export slot" + +msgid "Destination slot" +msgstr "Destination slot" + +msgid "Release import/export slot" +msgstr "Release import/export slot" + +msgid "Tape drives" +msgstr "Tape drives" + +msgid "Changer slots" +msgstr "Changer slots" + +msgid "Release all I/E slots" +msgstr "Release all I/E slots" + +msgid "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." +msgstr "There are to few regular slots to transfer selected volumes from import/export slots. Full import/export slot count: %ie_slot_count, free regular slot count: %slot_count." + +msgid "Tip: To use bulk autochanger actions, please select table rows." +msgstr "Tip: To use bulk autochanger actions, please select table rows." + +msgid "Mount volume" +msgstr "Mount volume" diff --git a/gui/baculum/protected/Web/Pages/StorageView.page b/gui/baculum/protected/Web/Pages/StorageView.page index ce24a2a4b..b29b26a33 100644 --- a/gui/baculum/protected/Web/Pages/StorageView.page +++ b/gui/baculum/protected/Web/Pages/StorageView.page @@ -31,6 +31,13 @@ Visible="<%=!empty($_SESSION['sd']) && $this->getIsAutochanger()%>" OnClick="setAutochanger" /> +
<%=Prado::localize('Mount')%>   @@ -70,8 +76,7 @@ CssClass="w3-button w3-green w3-margin-bottom" ValidationGroup="AutoChangerGroup" CausesValidation="true" - ClientSide.OnLoading="$('#status_storage_loading').show();" - ClientSide.OnSuccess="$('#status_storage_loading').hide();$('#storage_action_text_output').slideDown();" + ClientSide.OnLoading="oStorageActions.show_loader(true);" > <%=Prado::localize('Release')%>   @@ -81,26 +86,102 @@ CssClass="w3-button w3-green w3-margin-bottom" ValidationGroup="AutoChangerGroup" CausesValidation="true" - ClientSide.OnLoading="$('#status_storage_loading').show();" - ClientSide.OnSuccess="$('#status_storage_loading').hide();$('#storage_action_text_output').slideDown();" + ClientSide.OnLoading="oStorageActions.show_loader(true);" > <%=Prado::localize('Umount')%>   + + + +
- - - + + +
<%[ Slot number: ]%> - - + +