-module baculum-api 1.0.2;
+module baculum-api 1.0.3;
require {
type init_t;
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 ==============
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 };
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;
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.Common.Class.Errors');
+Prado::using('Application.API.Class.DeviceConfig');
+Prado::using('Application.API.Class.APIModule');
+
+/**
+ * Execute changer command module.
+ * Changer command should provide interface compatible with
+ * mtx-changer script provided with Bacula.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+}
+?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.Common.Class.ConfigFileModule');
+
+/**
+ * Manage devices configuration.
+ * Module is responsible for device config data.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+}
+?>
* 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
}
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;
+ }
}
?>
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"
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"
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"
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"
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.ChangerCommand');
+
+/**
+ * Load a given slot to autochanger drive.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+}
+?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.
+ */
+
+/**
+ * Which slot is loaded in autochanger drive.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+}
+?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.ChangerCommand');
+
+/**
+ * Unload a given slot from autochanger drive.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+}
+?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.
+ */
+
+/**
+ * List autochanger volume names (requires barcode reader).
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category API
+ * @package Baculum API
+ */
+class ChangerList extends BaculumAPIServer {
+
+ const LIST_PATTERN = '/^(?P<slot>\d+):(?P<volume>\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;
+ }
+}
+?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.
+ */
+
+/**
+ * Autochanger list all slots.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category API
+ * @package Baculum API
+ */
+class ChangerListAll extends BaculumAPIServer {
+
+ const LIST_ALL_DRIVE_PATTERN = '/^D:(?P<index>\d+):(?P<state>[EF]):?(?P<slot>\d+)?:?(?P<volume>\S+)?$/';
+ const LIST_ALL_SLOT_PATTERN = '/^S:(?P<slot>\d+):(?P<state>[EF]):?(?P<volume>\S+)?$/';
+ const LIST_ALL_IO_SLOT_PATTERN = '/^I:(?P<slot>\d+):(?P<state>[EF]):?(?P<volume>\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;
+ }
+}
+?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.
+ */
+
+/**
+ * How many slots has autochanger.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+}
+?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.ChangerCommand');
+
+/**
+ * Transfer autochanger tape from slot to slot.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+}
+?>
* 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
* Bacula(R) is a registered trademark of Kern Sibbald.
*/
+Prado::using('Application.API.Class.Bconsole');
+
/**
* Mount storage command endpoint.
*
* @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;
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.
+ */
+
+/**
+ * Mount storage command endpoint.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+ }
+}
+
+?>
* 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
* Bacula(R) is a registered trademark of Kern Sibbald.
*/
+Prado::using('Application.API.Class.Bconsole');
+
/**
* Release storage command endpoint.
*
* @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;
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.
+ */
+
+/**
+ * Release storage command endpoint.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+ }
+}
+
+?>
* 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
*
* Bacula(R) is a registered trademark of Kern Sibbald.
*/
+
+Prado::using('Application.API.Class.Bconsole');
/**
* Storage umount command endpoint.
* @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;
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.
+ */
+
+/**
+ * Storage umount command endpoint.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+ }
+}
+
+?>
$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;
}
<module id="api_config" class="Application.API.Class.APIConfig" />
<module id="bacula_config" class="Application.API.Class.BaculaConfig" />
<module id="bacula_setting" class="Application.API.Class.BaculaSetting" />
+ <module id="device_config" class="Application.API.Class.DeviceConfig" />
<!-- logging modules -->
<module id="log" class="System.Util.TLogRouter">
<route class="TFileLogRoute" Categories="Execute, External, Application, General, Security" LogPath="Application.API.Logs" LogFile="baculum-api.log" MaxFileSize="1000" MaxLogFiles="5" />
<!-- bconsole command modules -->
<module id="ls" class="Application.API.Class.Ls" />
<module id="list" class="Application.API.Class.BList" />
+ <!-- changer command modules -->
+ <module id="changer_command" class="Application.API.Class.ChangerCommand" />
</modules>
</configuration>
<url ServiceParameter="StorageMount" pattern="api/v2/storages/{id}/mount/" parameters.id="\d+" />
<url ServiceParameter="StorageUmount" pattern="api/v2/storages/{id}/umount/" parameters.id="\d+" />
<url ServiceParameter="StorageRelease" pattern="api/v2/storages/{id}/release/" parameters.id="\d+" />
+ <!-- devices endpoints -->
+ <url ServiceParameter="ChangerDriveLoad" pattern="api/v2/devices/{device_name}/load/" parameters.device_name="[a-zA-Z0-9:.\-_ ]+" />
+ <url ServiceParameter="ChangerDriveUnload" pattern="api/v2/devices/{device_name}/unload/" parameters.device_name="[a-zA-Z0-9:.\-_ ]+" />
+ <url ServiceParameter="ChangerDriveLoaded" pattern="api/v2/devices/{device_name}/loaded/" parameters.device_name="[a-zA-Z0-9:.\-_ ]+" />
+ <url ServiceParameter="ChangerList" pattern="api/v2/devices/{device_name}/list/" parameters.device_name="[a-zA-Z0-9:.\-_ ]+" />
+ <url ServiceParameter="ChangerListAll" pattern="api/v2/devices/{device_name}/listall/" parameters.device_name="[a-zA-Z0-9:.\-_ ]+" />
+ <url ServiceParameter="ChangerSlots" pattern="api/v2/devices/{device_name}/slots/" parameters.device_name="[a-zA-Z0-9:.\-_ ]+" />
+ <url ServiceParameter="ChangerSlotsTransfer" pattern="api/v2/devices/{device_name}/transfer/" parameters.device_name="[a-zA-Z0-9:.\-_ ]+" />
<!-- volumes (media) endpoints-->
<url ServiceParameter="Volumes" pattern="api/v2/volumes/" />
<url ServiceParameter="Volume" pattern="api/v2/volumes/{id}/" parameters.id="\d+" />
<url ServiceParameter="StoragesShow" pattern="api/v1/storages/show/" />
<url ServiceParameter="StorageShow" pattern="api/v1/storages/{id}/show/" parameters.id="\d+" />
<url ServiceParameter="StorageStatus" pattern="api/v1/storages/{id}/status/" parameters.id="\d+" />
- <url ServiceParameter="StorageMount" pattern="api/v1/storages/{id}/mount/" parameters.id="\d+" />
- <url ServiceParameter="StorageUmount" pattern="api/v1/storages/{id}/umount/" parameters.id="\d+" />
- <url ServiceParameter="StorageRelease" pattern="api/v1/storages/{id}/release/" parameters.id="\d+" />
+ <url ServiceParameter="StorageMountV1" pattern="api/v1/storages/{id}/mount/" parameters.id="\d+" />
+ <url ServiceParameter="StorageUmountV1" pattern="api/v1/storages/{id}/umount/" parameters.id="\d+" />
+ <url ServiceParameter="StorageReleaseV1" pattern="api/v1/storages/{id}/release/" parameters.id="\d+" />
<!-- volumes (media) endpoints-->
<url ServiceParameter="Volumes" pattern="api/v1/volumes/" />
<url ServiceParameter="Volume" pattern="api/v1/volumes/{id}/" parameters.id="\d+" />
--- /dev/null
+<%@ MasterClass="Application.API.Layouts.Main" Theme="Baculum-v2"%>
+<com:TContent ID="Main">
+ <header class="w3-container w3-block">
+ <h5>
+ <i class="fas fa-user-shield"></i> <%[ Devices ]%>
+ </h5>
+ </header>
+ <div class="w3-bar w3-green w3-margin-bottom">
+ <button type="button" id="btn_devices_autochanger" class="w3-bar-item w3-button tab_btn w3-grey" onclick="W3Tabs.open(this.id, 'devices_autochanger');"><%[ Autochangers ]%></button>
+ <button type="button" id="btn_devices_device" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'devices_device'); oDeviceList.table.responsive.recalc();"><%[ Devices ]%></button>
+ </div>
+ <div class="w3-container tab_item" id="devices_autochanger">
+ <a href="javascript:void(0)" class="w3-button w3-green w3-margin-bottom" onclick="oAPIAutochangers.add_autochanger();">
+ <i class="fas fa-plus"></i> <%[ Add autochanger ]%>
+ </a>
+ <table id="autochanger_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+ <thead>
+ <tr>
+ <th></th>
+ <th><%[ Name ]%></th>
+ <th><%[ Device ]%></th>
+ <th><%[ Actions ]%></th>
+ </tr>
+ </thead>
+ <tbody id="autochanger_list_body"></tbody>
+ <tfoot>
+ <tr>
+ <th></th>
+ <th><%[ Name ]%></th>
+ <th><%[ Device ]%></th>
+ <th><%[ Actions ]%></th>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ <com:Application.API.Portlets.SudoConfig ID="SudoConfig" />
+<script>
+var oAutochangerList = {
+ ids: {
+ autochanger_list: 'autochanger_list',
+ autochanger_list_body: 'autochanger_list_body'
+ },
+ table: null,
+ data: [],
+ init: function() {
+ if (!this.table) {
+ this.set_table();
+ } else {
+ var page = this.table.page();
+ this.table.clear().rows.add(this.data).draw();
+ this.table.page(page).draw(false);
+ }
+ },
+ set_table: function() {
+ this.table = $('#' + this.ids.autochanger_list).DataTable({
+ data: this.data,
+ deferRender: true,
+ dom: 'lBfrtip',
+ stateSave: true,
+ buttons: [
+ 'copy', 'csv', 'colvis'
+ ],
+ columns: [
+ {
+ className: 'details-control',
+ orderable: false,
+ data: null,
+ defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+ },
+ {data: 'name'},
+ {data: 'device'},
+ {
+ data: 'name',
+ render: function(data, type, row) {
+ var span = document.createElement('SPAN');
+ span.className = 'w3-right';
+
+ var chpwd_btn = document.createElement('BUTTON');
+ chpwd_btn.className = 'w3-button w3-green';
+ chpwd_btn.type = 'button';
+ var i = document.createElement('I');
+ i.className = 'fas fa-edit';
+ var label = document.createTextNode(' <%[ Edit ]%>');
+ chpwd_btn.appendChild(i);
+ chpwd_btn.innerHTML += ' ';
+ chpwd_btn.appendChild(label);
+ chpwd_btn.setAttribute('onclick', 'oAPIAutochangers.edit_autochanger("' + data + '")');
+
+ var del_btn = document.createElement('BUTTON');
+ del_btn.className = 'w3-button w3-red w3-margin-left';
+ del_btn.type = 'button';
+ var i = document.createElement('I');
+ i.className = 'fas fa-trash-alt';
+ var label = document.createTextNode(' <%[ Delete ]%>');
+ del_btn.appendChild(i);
+ del_btn.innerHTML += ' ';
+ del_btn.appendChild(label);
+ del_btn.setAttribute('onclick', 'oAPIAutochangers.delete_autochanger("' + data + '")');
+
+ span.appendChild(chpwd_btn);
+ span.appendChild(del_btn);
+
+ return span.outerHTML;
+ }
+ }
+ ],
+ responsive: {
+ details: {
+ type: 'column'
+ }
+ },
+ columnDefs: [{
+ className: 'control',
+ orderable: false,
+ targets: 0
+ },
+ {
+ className: "dt-center",
+ targets: [ 3 ]
+ }],
+ order: [1, 'asc'],
+ });
+ }
+};
+var bin_fields = {
+ changer_command: [
+ '<%=$this->ChangerCommand->ClientID%>'
+ ]
+};
+var bin_opts = {
+ changer_command: {
+ base_path: true
+ }
+};
+oSudoConfig.set_bin_fields(bin_fields);
+oSudoConfig.set_bin_opts(bin_opts);
+</script>
+ <div id="autochanger_window" class="w3-modal">
+ <div class="w3-modal-content w3-animate-top w3-card-4">
+ <header class="w3-container w3-teal">
+ <span onclick="document.getElementById('autochanger_window').style.display = 'none';" class="w3-button w3-display-topright">×</span>
+ <h2 id="autochanger_window_title_add" style="display: none"><%[ Add autochanger ]%></h2>
+ <h2 id="autochanger_window_title_edit" style="display: none"><%[ Edit autochanger ]%></h2>
+ </header>
+ <div class="w3-container w3-margin-left w3-margin-right w3-text-teal">
+ <span id="autochanger_exists" class="error" style="display: none"><ul><li><%[ Autochanger with the given name already exists. ]%></li></ul></span>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><label for="autochanger_config"><%[ Copy from Bacula SD config: ]%></label></div>
+ <div class="w3-half">
+ <select id="autochanger_config" class="w3-select w3-border"></select>
+ </div>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><com:TLabel ForControl="AutochangerName" Text="<%[ Name: ]%>" /></div>
+ <div class="w3-half">
+ <com:TActiveTextBox
+ ID="AutochangerName"
+ AutoPostBack="false"
+ MaxLength="100"
+ CssClass="w3-input w3-border"
+ Attributes.placeholder="My Autochanger 123"
+ />
+ <com:TRequiredFieldValidator
+ ValidationGroup="AutchangerGroup"
+ ControlToValidate="AutochangerName"
+ ErrorMessage="<%[ Field cannot be empty. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ <com:TRegularExpressionValidator
+ ValidationGroup="AutchangerGroup"
+ RegularExpression="<%=DeviceConfig::DEVICE_PATH_PATTERN%>"
+ ControlToValidate="AutochangerName"
+ ErrorMessage="<%[ Invalid value. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ </div> <i id="device_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><com:TLabel ForControl="ChangerDevice" Text="<%[ Changer device: ]%>" /></div>
+ <div class="w3-half">
+ <com:TActiveTextBox
+ ID="ChangerDevice"
+ AutoPostBack="false"
+ MaxLength="100"
+ CssClass="w3-input w3-border"
+ Attributes.placeholder="/dev/sg4"
+ />
+ <com:TRequiredFieldValidator
+ ValidationGroup="AutchangerGroup"
+ ControlToValidate="ChangerDevice"
+ ErrorMessage="<%[ Field cannot be empty. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ </div> <i id="device_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><com:TLabel ForControl="ChangerCommand" Text="<%[ Changer command: ]%>" /></div>
+ <div class="w3-half">
+ <com:TActiveTextBox
+ ID="ChangerCommand"
+ AutoPostBack="false"
+ MaxLength="100"
+ CssClass="w3-input w3-border"
+ Attributes.placeholder="/some/path/mtx-changer %c %o %S %a %d"
+ />
+ <com:TRequiredFieldValidator
+ ValidationGroup="AutchangerGroup"
+ ControlToValidate="ChangerCommand"
+ ErrorMessage="<%[ Field cannot be empty. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ </div> <i id="device_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third">
+ <com:TLabel
+ ForControl="ChangerCommandUseSudo"
+ Text="<%[ Use sudo: ]%>"
+ />
+ </div>
+ <div class="w3-col w3-half">
+ <com:TActiveCheckBox
+ ID="ChangerCommandUseSudo"
+ CssClass="w3-check"
+ /> <a href="javascript:void(0)" onclick="oSudoConfig.get_config('changer_command');"><%[ Get sudo configuration ]%></a>
+ </div>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third">
+ <com:TLabel
+ ForControl="ChangerCommandTest"
+ Text="<%[ Changer command test: ]%>"
+ /></div>
+ <div class="w3-col w3-half">
+ <table>
+ <tr>
+ <td>
+ <com:TActiveLinkButton
+ ID="ChangerCommandTest"
+ CssClass="w3-button w3-green"
+ CausesValidation="false"
+ OnCallback="testChangerCommand"
+ >
+ <prop:ClientSide.OnLoading>
+ $('#changer_command_test_result_ok').hide();
+ $('#changer_command_test_result_err').hide();
+ $('#<%=$this->ChangerCommandTestResultErr->ClientID%>').hide();
+ $('#changer_command_test_loader').show();
+ </prop:ClientSide.OnLoading>
+ <prop:ClientSide.OnComplete>
+ $('#changer_command_test_loader').hide();
+ </prop:ClientSide.OnComplete>
+ <i class="fas fa-play"></i> <%[ test ]%>
+ </com:TActiveLinkButton>
+ </td>
+ <td style="padding-left: 10px">
+ <span id="changer_command_test_loader" style="display: none">
+ <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+ </span>
+ <span id="changer_command_test_result_ok" class="w3-text-green" style="display: none">
+ <i class="fas fa-check"></i> <%[ OK ]%>
+ </span>
+ <span id="changer_command_test_result_err" class="w3-text-red" style="display: none">
+ <i class="fas fa-exclamation-circle"></i>
+ </span>
+ <com:TActiveLabel ID="ChangerCommandTestResultErr" CssClass="w3-text-red" Display="None"><%[ Changer command error ]%></com:TActiveLabel>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><com:TLabel ForControl="ChangerCommand" Text="<%[ Devices: ]%>" /></div>
+ <div class="w3-half">
+ <com:TActiveListBox
+ ID="ChangerDevices"
+ SelectionMode="Multiple"
+ CssClass="w3-input w3-border"
+ AutoPostBack="false"
+ />
+ <com:TRequiredFieldValidator
+ ValidationGroup="AutchangerGroup"
+ ControlToValidate="ChangerDevices"
+ ErrorMessage="<%[ Field cannot be empty. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ <p class="w3-text-black" style="margin-top: 8px;"><%[ Use CTRL + left-click to multiple item selection ]%></p>
+ </div> <i id="device_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+ </div>
+ <footer class="w3-container w3-center">
+ <button type="button" class="w3-button w3-red" onclick="document.getElementById('autochanger_window').style.display = 'none';"><i class="fas fa-times"></i> <%[ Cancel ]%></button>
+ <com:TActiveLinkButton
+ ID="AutochangerSave"
+ ValidationGroup="AutchangerGroup"
+ CausesValidation="true"
+ OnCallback="saveAutochanger"
+ CssClass="w3-button w3-section w3-teal w3-padding"
+ >
+ <i class="fa fa-save"></i> <%[ Save ]%>
+ </com:TActiveLinkButton>
+ </footer>
+ <com:TActiveHiddenField ID="AutochangerWindowType" />
+ </div>
+ </div>
+ </div>
+<com:TCallback ID="AutochangerList" OnCallback="setAutochangerList" />
+<com:TCallback ID="LoadAutochanger" OnCallback="loadAutochanger" />
+<com:TCallback ID="AddAutochanger" OnCallback="addAutochanger" />
+<com:TCallback ID="DeleteAutochanger" OnCallback="deleteAutochanger" />
+<script>
+var oAPIAutochangers = {
+ ids: {
+ autochanger_window: 'autochanger_window',
+ autochanger_name: '<%=$this->AutochangerName->ClientID%>',
+ autochanger_device: '<%=$this->ChangerDevice->ClientID%>',
+ autochanger_command: '<%=$this->ChangerCommand->ClientID%>',
+ autochanger_devices: '<%=$this->ChangerDevices->ClientID%>',
+ title_add: 'autochanger_window_title_add',
+ title_edit: 'autochanger_window_title_edit',
+ window_type: '<%=$this->AutochangerWindowType->ClientID%>',
+ autochanger_exists: 'autochanger_exists',
+ autochanger_config: 'autochanger_config'
+ },
+ achs_config: {},
+ init: function() {
+ this.set_events();
+ this.load_autochanger_list();
+ },
+ set_events: function() {
+ var send_form = function(e) {
+ var kc = e.which || e.keyCode;
+ if (kc == 13) {
+ $('#<%=$this->AutochangerSave->ClientID%>').click();
+ }
+ };
+ [
+ this.ids.autochanger_name,
+ this.ids.autochanger_device,
+ this.ids.autochanger_command,
+ this.ids.autochanger_devices
+ ].forEach(function(id) {
+ document.getElementById(id).addEventListener('keypress', send_form);
+ });
+ var ach_conf = document.getElementById(this.ids.autochanger_config);
+ ach_conf.addEventListener('change', function(e) {
+ var name = document.getElementById(this.ids.autochanger_name);
+ var device = document.getElementById(this.ids.autochanger_device);
+ var command = document.getElementById(this.ids.autochanger_command);
+ if (ach_conf.value) {
+ name.value = this.achs_config[ach_conf.value].Name;
+ device.value = this.achs_config[ach_conf.value].ChangerDevice;
+ command.value = this.achs_config[ach_conf.value].ChangerCommand;
+ } else {
+ name.value = device.value = command.value = '';
+ }
+ }.bind(this));
+ },
+ show_autochanger_window: function(show, name) {
+ oAPIAutochangers.hide_errors();
+ oAPIAutochangers.clear_fields();
+ var win = document.getElementById(oAPIAutochangers.ids.autochanger_window);
+ if (show) {
+ win.style.display = 'block';
+ } else {
+ win.style.display = 'none';
+ }
+ var title_add = document.getElementById(oAPIAutochangers.ids.title_add);
+ var title_edit = document.getElementById(oAPIAutochangers.ids.title_edit);
+ var window_type = document.getElementById(oAPIAutochangers.ids.window_type);
+ var autochanger_name = document.getElementById(oAPIAutochangers.ids.autochanger_name);
+ if (name) {
+ // edit autochanger
+ this.load_autochanger_window(name);
+ title_add.style.display = 'none';
+ title_edit.style.display = '';
+ window_type.value = '<%=APIDevices::WINDOW_TYPE_EDIT%>';
+ autochanger_name.setAttribute('readonly', true);
+ } else {
+ // add new autochanger
+ title_edit.style.display = 'none';
+ title_add.style.display = '';
+ window_type.value = '<%=APIDevices::WINDOW_TYPE_ADD%>';
+ autochanger_name.removeAttribute('readonly');
+ autochanger_name.focus();
+ }
+ },
+ load_autochanger_window: function(name) {
+ var cb = <%=$this->LoadAutochanger->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(name);
+ cb.dispatch();
+ },
+ load_autochanger_list: function() {
+ var cb = <%=$this->AutochangerList->ActiveControl->Javascript%>;
+ cb.dispatch();
+ },
+ load_autochanger_list_cb: function(autochangers) {
+ oAutochangerList.data = autochangers;
+ oAutochangerList.init();
+ },
+ add_autochanger: function() {
+ var cb = <%=$this->AddAutochanger->ActiveControl->Javascript%>;
+ cb.dispatch();
+ this.show_autochanger_window(true);
+ },
+ edit_autochanger: function(name) {
+ this.show_autochanger_window(true, name);
+ },
+ delete_autochanger: function(name) {
+ if (!confirm('<%[ Are you sure? ]%>')) {
+ return false;
+ }
+ var cb = <%=$this->DeleteAutochanger->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(name);
+ cb.dispatch();
+ },
+ set_disabled_drives: function(indices) {
+ oAPIAutochangers.clear_disabled_drives();
+ var devices = document.getElementById(oAPIAutochangers.ids.autochanger_devices);
+ for (var i = 0; i < indices.length; i++) {
+ devices.options[indices[i]].setAttribute('disabled', 'disabled');
+ }
+ },
+ clear_disabled_drives: function() {
+ var devices = document.getElementById(oAPIAutochangers.ids.autochanger_devices);
+ for (var i = 0; i < devices.options.length; i++) {
+ if (devices.options[i].hasAttribute('disabled')) {
+ devices.options[i].removeAttribute('disabled');
+ }
+ }
+ },
+ hide_errors: function() {
+ document.getElementById(this.ids.autochanger_exists).style.display = 'none';
+ // changer command test result
+ $('#changer_command_test_result_ok').hide();
+ $('#changer_command_test_result_err').hide();
+ $('#<%=$this->ChangerCommandTestResultErr->ClientID%>').hide();
+ },
+ clear_fields: function() {
+ [
+ this.ids.autochanger_name,
+ this.ids.autochanger_device,
+ this.ids.autochanger_command,
+ this.ids.autochanger_devices
+ ].forEach(function(id) {
+ document.getElementById(id).value = '';
+ });
+ },
+ set_config_autochangers: function(achs) {
+ if (achs.exitcode !== 0) {
+ return false;
+ }
+
+ var combo = document.getElementById(oAPIAutochangers.ids.autochanger_config);
+ var opt = document.createElement('OPTION');
+ opt.value = '';
+ var label = document.createTextNode('');
+ opt.appendChild(label);
+ combo.appendChild(opt);
+
+ for (var i = 0; i < achs.output.length; i++) {
+ opt = document.createElement('OPTION');
+ opt.value = achs.output[i].Autochanger.Name;
+ label = document.createTextNode(achs.output[i].Autochanger.Name);
+ opt.appendChild(label);
+ combo.appendChild(opt);
+ oAPIAutochangers.achs_config[achs.output[i].Autochanger.Name] = achs.output[i].Autochanger;
+ }
+ }
+};
+$(function() {
+ oAPIAutochangers.init();
+});
+</script>
+ <div class="w3-container tab_item" id="devices_device" style="display: none">
+ <a href="javascript:void(0)" class="w3-button w3-green w3-margin-bottom" onclick="oAPIDevices.show_device_window(true);">
+ <i class="fas fa-plus"></i> <%[ Add device ]%>
+ </a>
+ <table id="device_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+ <thead>
+ <tr>
+ <th></th>
+ <th><%[ Name ]%></th>
+ <th><%[ Device ]%></th>
+ <th><%[ Drive index ]%></th>
+ <th><%[ Autochanger ]%></th>
+ <th><%[ Actions ]%></th>
+ </tr>
+ </thead>
+ <tbody id="device_list_body"></tbody>
+ <tfoot>
+ <tr>
+ <th></th>
+ <th><%[ Name ]%></th>
+ <th><%[ Device ]%></th>
+ <th><%[ Drive index ]%></th>
+ <th><%[ Autochanger ]%></th>
+ <th><%[ Actions ]%></th>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+<script>
+var oDeviceList = {
+ ids: {
+ device_list: 'device_list',
+ device_list_body: 'device_list_body'
+ },
+ table: null,
+ data: [],
+ init: function() {
+ if (!this.table) {
+ this.set_table();
+ } else {
+ var page = this.table.page();
+ this.table.clear().rows.add(this.data).draw();
+ this.table.page(page).draw(false);
+ }
+ },
+ set_table: function() {
+ this.table = $('#' + this.ids.device_list).DataTable({
+ data: this.data,
+ deferRender: true,
+ dom: 'lBfrtip',
+ stateSave: true,
+ buttons: [
+ 'copy', 'csv', 'colvis'
+ ],
+ columns: [
+ {
+ className: 'details-control',
+ orderable: false,
+ data: null,
+ defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+ },
+ {data: 'name'},
+ {data: 'device'},
+ {data: 'index'},
+ {data: 'autochanger'},
+ {
+ data: 'name',
+ render: function(data, type, row) {
+ var span = document.createElement('SPAN');
+ span.className = 'w3-right';
+
+ var chpwd_btn = document.createElement('BUTTON');
+ chpwd_btn.className = 'w3-button w3-green';
+ chpwd_btn.type = 'button';
+ var i = document.createElement('I');
+ i.className = 'fas fa-edit';
+ var label = document.createTextNode(' <%[ Edit ]%>');
+ chpwd_btn.appendChild(i);
+ chpwd_btn.innerHTML += ' ';
+ chpwd_btn.appendChild(label);
+ chpwd_btn.setAttribute('onclick', 'oAPIDevices.edit_device("' + data + '")');
+
+ var del_btn = document.createElement('BUTTON');
+ del_btn.className = 'w3-button w3-red w3-margin-left';
+ del_btn.type = 'button';
+ var i = document.createElement('I');
+ i.className = 'fas fa-trash-alt';
+ var label = document.createTextNode(' <%[ Delete ]%>');
+ del_btn.appendChild(i);
+ del_btn.innerHTML += ' ';
+ del_btn.appendChild(label);
+ del_btn.setAttribute('onclick', 'oAPIDevices.delete_device("' + data + '", "' + row.autochanger + '")');
+
+ span.appendChild(chpwd_btn);
+ span.appendChild(del_btn);
+
+ return span.outerHTML;
+ }
+ }
+ ],
+ responsive: {
+ details: {
+ type: 'column'
+ }
+ },
+ columnDefs: [{
+ className: 'control',
+ orderable: false,
+ targets: 0
+ },
+ {
+ className: "dt-center",
+ targets: [ 3, 5 ]
+ }],
+ order: [1, 'asc'],
+ initComplete: function () {
+ oDeviceList.set_filters(this.api());
+ }
+ });
+ },
+ set_filters: function(api) {
+ api.columns([2, 3, 4]).every(function () {
+ var column = this;
+ var select = $('<select><option value=""></option></select>')
+ .appendTo($(column.footer()).empty())
+ .on('change', function () {
+ var val = dtEscapeRegex(
+ $(this).val()
+ );
+ column
+ .search(val ? '^' + val + '$' : '', true, false)
+ .draw();
+ });
+ column.data().unique().sort().each(function (d, j) {
+ if (column.search() == '^' + dtEscapeRegex(d) + '$') {
+ select.append('<option value="' + d + '" title="' + d + '" selected>' + d + '</option>');
+ } else if (d) {
+ select.append('<option value="' + d + '" title="' + d + '">' + d + '</option>');
+ }
+ });
+ });
+ },
+};
+</script>
+ <div id="device_window" class="w3-modal">
+ <div class="w3-modal-content w3-animate-top w3-card-4">
+ <header class="w3-container w3-teal">
+ <span onclick="document.getElementById('device_window').style.display = 'none';" class="w3-button w3-display-topright">×</span>
+ <h2 id="device_window_title_add" style="display: none"><%[ Add device ]%></h2>
+ <h2 id="device_window_title_edit" style="display: none"><%[ Edit device ]%></h2>
+ </header>
+ <div class="w3-container w3-margin-left w3-margin-right w3-text-teal">
+ <span id="device_exists" class="error" style="display: none"><ul><li><%[ Device with the given name already exists. ]%></li></ul></span>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><label for="device_config"><%[ Copy from Bacula SD config: ]%></label></div>
+ <div class="w3-half">
+ <select id="device_config" class="w3-select w3-border"></select>
+ </div>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><com:TLabel ForControl="DeviceName" Text="<%[ Name: ]%>" /></div>
+ <div class="w3-half">
+ <com:TActiveTextBox
+ ID="DeviceName"
+ AutoPostBack="false"
+ MaxLength="100"
+ CssClass="w3-input w3-border"
+ Attributes.placeholder="My Device 123"
+ />
+ <com:TRequiredFieldValidator
+ ValidationGroup="DeviceGroup"
+ ControlToValidate="DeviceName"
+ ErrorMessage="<%[ Field cannot be empty. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ <com:TRegularExpressionValidator
+ ValidationGroup="DeviceGroup"
+ RegularExpression="<%=DeviceConfig::DEVICE_PATH_PATTERN%>"
+ ControlToValidate="DeviceName"
+ ErrorMessage="<%[ Invalid value. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ </div> <i id="device_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><com:TLabel ForControl="ChangerDevice" Text="<%[ Device path: ]%>" /></div>
+ <div class="w3-half">
+ <com:TActiveTextBox
+ ID="DeviceDevice"
+ AutoPostBack="false"
+ MaxLength="100"
+ CssClass="w3-input w3-border"
+ Attributes.placeholder="/dev/nst1"
+ />
+ <com:TRequiredFieldValidator
+ ValidationGroup="DeviceGroup"
+ ControlToValidate="DeviceDevice"
+ ErrorMessage="<%[ Field cannot be empty. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ </div> <i id="device_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third"><com:TLabel ForControl="ChangerCommand" Text="<%[ Drive index: ]%>" /></div>
+ <div class="w3-half">
+ <com:TActiveTextBox
+ ID="DeviceIndex"
+ AutoPostBack="false"
+ MaxLength="4"
+ Style.Width="100px"
+ CssClass="w3-input w3-border"
+ Attributes.placeholder="0"
+ />
+ <com:TRequiredFieldValidator
+ ValidationGroup="DeviceGroup"
+ ControlToValidate="DeviceIndex"
+ ErrorMessage="<%[ Field cannot be empty. ]%>"
+ ControlCssClass="field_invalid"
+ Display="Dynamic"
+ />
+ </div> <i id="device_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+ </div>
+ <footer class="w3-container w3-center">
+ <button type="button" class="w3-button w3-red" onclick="document.getElementById('device_window').style.display = 'none';"><i class="fas fa-times"></i> <%[ Cancel ]%></button>
+ <com:TActiveLinkButton
+ ID="DeviceSave"
+ ValidationGroup="DeviceGroup"
+ CausesValidation="true"
+ OnCallback="saveDevice"
+ CssClass="w3-button w3-section w3-teal w3-padding"
+ >
+ <i class="fa fa-save"></i> <%[ Save ]%>
+ </com:TActiveLinkButton>
+ </footer>
+ <com:TActiveHiddenField ID="DeviceWindowType" />
+ </div>
+ </div>
+ </div>
+<com:TCallback ID="DeviceList" OnCallback="setDeviceList" />
+<com:TCallback ID="LoadDevice" OnCallback="loadDevice" />
+<com:TCallback ID="DeleteDevice" OnCallback="deleteDevice" />
+<script>
+var oAPIDevices = {
+ ids: {
+ device_window: 'device_window',
+ device_name: '<%=$this->DeviceName->ClientID%>',
+ device_device: '<%=$this->DeviceDevice->ClientID%>',
+ device_index: '<%=$this->DeviceIndex->ClientID%>',
+ title_add: 'device_window_title_add',
+ title_edit: 'device_window_title_edit',
+ window_type: '<%=$this->DeviceWindowType->ClientID%>',
+ device_exists: 'device_exists',
+ device_config: 'device_config'
+ },
+ devs_config: {},
+ init: function() {
+ this.set_events();
+ this.load_device_list();
+ },
+ set_events: function() {
+ var send_form = function(e) {
+ var kc = e.which || e.keyCode;
+ if (kc == 13) {
+ $('#<%=$this->DeviceSave->ClientID%>').click();
+ }
+ };
+ [
+ this.ids.device_name,
+ this.ids.device_device,
+ this.ids.device_index
+ ].forEach(function(id) {
+ document.getElementById(id).addEventListener('keypress', send_form);
+ });
+ var dev_conf = document.getElementById(this.ids.device_config);
+ dev_conf.addEventListener('change', function(e) {
+ var name = document.getElementById(this.ids.device_name);
+ var device = document.getElementById(this.ids.device_device);
+ var index = document.getElementById(this.ids.device_index);
+ if (dev_conf.value) {
+ name.value = this.devs_config[dev_conf.value].Name;
+ device.value = this.devs_config[dev_conf.value].ArchiveDevice;
+ index.value = this.devs_config[dev_conf.value].DriveIndex;
+ } else {
+ name.value = device.value = index.value = '';
+ }
+ }.bind(this));
+ },
+ show_device_window: function(show, name) {
+ oAPIDevices.hide_errors();
+ oAPIDevices.clear_fields();
+ var win = document.getElementById(oAPIDevices.ids.device_window);
+ if (show) {
+ win.style.display = 'block';
+ } else {
+ win.style.display = 'none';
+ }
+ var title_add = document.getElementById(oAPIDevices.ids.title_add);
+ var title_edit = document.getElementById(oAPIDevices.ids.title_edit)
+ var window_type = document.getElementById(oAPIDevices.ids.window_type);
+ var device_name = document.getElementById(oAPIDevices.ids.device_name);
+ if (name) {
+ // edit device
+ oAPIDevices.load_device_window(name);
+ window_type.value = '<%=APIDevices::WINDOW_TYPE_EDIT%>';
+ title_add.style.display = 'none';
+ title_edit.style.display = '';
+ device_name.setAttribute('readonly', true);
+ } else {
+ // add new device
+ window_type.value = '<%=APIDevices::WINDOW_TYPE_ADD%>';
+ title_edit.style.display = 'none';
+ title_add.style.display = '';
+ device_name.removeAttribute('readonly');
+ device_name.focus();
+ }
+ },
+ load_device_window: function(name) {
+ var cb = <%=$this->LoadDevice->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(name);
+ cb.dispatch();
+ },
+ load_device_list: function() {
+ var cb = <%=$this->DeviceList->ActiveControl->Javascript%>;
+ cb.dispatch();
+ },
+ load_device_list_cb: function(devices) {
+ oDeviceList.data = devices;
+ oDeviceList.init();
+ },
+ edit_device: function(name) {
+ this.show_device_window(true, name);
+ },
+ delete_device: function(name, autochanger) {
+ if (autochanger) {
+ var emsg = "<%[ Unable to delete device. Please unassign it from autochanger '%s' first. ]%>";
+ emsg = emsg.replace('%s', autochanger);
+ alert(emsg);
+ return false;
+ }
+ if (!confirm('<%[ Are you sure? ]%>')) {
+ return false;
+ }
+ var cb = <%=$this->DeleteDevice->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(name);
+ cb.dispatch();
+ },
+ hide_errors: function() {
+ document.getElementById(this.ids.device_exists).style.display = 'none';
+ },
+ clear_fields: function() {
+ [
+ this.ids.device_name,
+ this.ids.device_device,
+ this.ids.device_index
+ ].forEach(function(id) {
+ document.getElementById(id).value = '';
+ });
+ },
+ set_config_devices: function(devs) {
+ if (devs.exitcode !== 0) {
+ return false;
+ }
+
+ var combo = document.getElementById(oAPIDevices.ids.device_config);
+ var opt = document.createElement('OPTION');
+ opt.value = '';
+ var label = document.createTextNode('');
+ opt.appendChild(label);
+ combo.appendChild(opt);
+
+ for (var i = 0; i < devs.output.length; i++) {
+ opt = document.createElement('OPTION');
+ opt.value = devs.output[i].Device.Name;
+ label = document.createTextNode(devs.output[i].Device.Name);
+ opt.appendChild(label);
+ combo.appendChild(opt);
+ oAPIDevices.devs_config[devs.output[i].Device.Name] = devs.output[i].Device;
+ }
+ }
+};
+$(function() {
+ oAPIDevices.init();
+});
+</script>
+</com:TContent>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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.DeviceConfig');
+Prado::using('Application.API.Class.BAPIException');
+Prado::using('Application.API.Class.BaculumAPIPage');
+
+/**
+ * API devices page.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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]
+ );
+ }
+}
+?>
<com:TCheckBox
ID="UseSudo"
CssClass="w3-check"
- /> <a href="javascript:void(0)" onclick="get_sudo_config('bconsole');"><%[ Get sudo configuration ]%></a>
+ /> <a href="javascript:void(0)" onclick="oSudoConfig.get_config('bconsole');"><%[ Get sudo configuration ]%></a>
</div>
</div>
<com:TCheckBox
ID="BJSONUseSudo"
CssClass="w3-check"
- /> <a href="javascript:void(0)" onclick="get_sudo_config('config');"><%[ Get sudo configuration ]%></a>
+ /> <a href="javascript:void(0)" onclick="oSudoConfig.get_config('config');"><%[ Get sudo configuration ]%></a>
</div>
</div>
</fieldset>
</fieldset>
</com:TWizardStep>
</com:TWizard>
-<com:TJuiDialog
- ID="SudoConfigPopup"
- Options.title="<%[ Sudo configuration ]%>"
- Options.autoOpen="false",
- Options.minWidth="820"
- Options.minHeight="200"
->
- <p><%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%></p>
- <p><strong><%[ Note ]%></strong> <%[ Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file. ]%>
- <p><%[ Example sudo configuration for Apache web server user (RHEL, CentOS and others): ]%></p>
- <pre id="sudo_config_apache"></pre>
- <p><%[ Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others): ]%></p>
- <pre id="sudo_config_lighttpd"></pre>
- <p><%[ Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others): ]%></p>
- <pre id="sudo_config_www_data"></pre>
-</com:TJuiDialog>
+ <com:Application.API.Portlets.SudoConfig ID="SudoConfig" />
<script type="text/javascript">
var bjsontools_fields = {
General: {
result.textContent = '';
};
-function get_sudo_config(type) {
- var bin_fields = {
- bconsole: [
- '<%=$this->BconsolePath->ClientID%>'
- ],
- config: [
- '<%=$this->BDirJSONPath->ClientID%>',
- '<%=$this->BSdJSONPath->ClientID%>',
- '<%=$this->BFdJSONPath->ClientID%>',
- '<%=$this->BBconsJSONPath->ClientID%>',
- ]
- }
- var val, pre;
- var cfg = '';
- var users = ['apache', 'lighttpd', 'www-data'];
- var fields = bin_fields.hasOwnProperty(type) ? bin_fields[type] : [];
- for (var i = 0; i < users.length; i++) {
- var pre = document.getElementById('sudo_config_' + users[i].replace(/-/g, '_'));
- pre.textContent = 'Defaults:' + users[i] + ' !requiretty' + "\n";
- for (var j = 0; j < fields.length; j++) {
- val = document.getElementById(fields[j]).value.trim();
- if (val) {
- pre.textContent += users[i] + ' ALL = (root) NOPASSWD: ' + val + "\n";
- }
- }
- }
- $('#<%=$this->SudoConfigPopup->ClientID%>').dialog('open');
-}
+var bin_fields = {
+ bconsole: [
+ '<%=$this->BconsolePath->ClientID%>'
+ ],
+ config: [
+ '<%=$this->BDirJSONPath->ClientID%>',
+ '<%=$this->BSdJSONPath->ClientID%>',
+ '<%=$this->BFdJSONPath->ClientID%>',
+ '<%=$this->BBconsJSONPath->ClientID%>',
+ ]
+};
+oSudoConfig.set_bin_fields(bin_fields);
</script>
</com:TContent>
<com:TCheckBox
ID="BconsoleUseSudo"
CssClass="w3-check"
- /> <a href="javascript:void(0)" onclick="get_sudo_config('bconsole');"><%[ Get sudo configuration ]%></a>
+ /> <a href="javascript:void(0)" onclick="oSudoConfig.get_config('bconsole');"><%[ Get sudo configuration ]%></a>
</div>
</div>
<div class="w3-row w3-section">
<com:TCheckBox
ID="BJSONUseSudo"
CssClass="w3-check"
- /> <a href="javascript:void(0)" onclick="get_sudo_config('config');"><%[ Get sudo configuration ]%></a>
+ /> <a href="javascript:void(0)" onclick="oSudoConfig.get_config('config');"><%[ Get sudo configuration ]%></a>
</div>
</div>
</fieldset>
<com:TCheckBox
ID="ActionsUseSudo"
CssClass="w3-check"
- /> <a href="javascript:void(0)" onclick="get_sudo_config('actions');"><%[ Get sudo configuration ]%></a>
+ /> <a href="javascript:void(0)" onclick="oSudoConfig.get_config('actions');"><%[ Get sudo configuration ]%></a>
</div>
</div>
</fieldset>
</div>
</div>
</div>
-<com:TJuiDialog
- ID="SudoConfigPopup"
- Options.title="<%[ Sudo configuration ]%>"
- Options.autoOpen="false",
- Options.minWidth="820"
- Options.minHeight="200"
->
- <p><%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%></p>
- <p><strong><%[ Note ]%></strong> <%[ Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file. ]%>
- <p><%[ Example sudo configuration for Apache web server user (RHEL, CentOS and others): ]%></p>
- <pre id="sudo_config_apache"></pre>
- <p><%[ Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others): ]%></p>
- <pre id="sudo_config_lighttpd"></pre>
- <p><%[ Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others): ]%></p>
- <pre id="sudo_config_www_data"></pre>
-</com:TJuiDialog>
+ <com:Application.API.Portlets.SudoConfig ID="SudoConfig" />
<script>
-function get_sudo_config(type) {
- var bin_fields = {
- bconsole: [
- '<%=$this->BconsolePath->ClientID%>'
- ],
- config: [
- '<%=$this->BDirJSONPath->ClientID%>',
- '<%=$this->BSdJSONPath->ClientID%>',
- '<%=$this->BFdJSONPath->ClientID%>',
- '<%=$this->BBconsJSONPath->ClientID%>',
- ],
- actions: [
- '<%=$this->DirStartAction->ClientID%>',
- '<%=$this->DirStopAction->ClientID%>',
- '<%=$this->DirRestartAction->ClientID%>',
- '<%=$this->SdStartAction->ClientID%>',
- '<%=$this->SdStopAction->ClientID%>',
- '<%=$this->SdRestartAction->ClientID%>',
- '<%=$this->FdStartAction->ClientID%>',
- '<%=$this->FdStopAction->ClientID%>',
- '<%=$this->FdRestartAction->ClientID%>'
- ]
- }
- var val, pre;
- var cfg = '';
- var users = ['apache', 'lighttpd', 'www-data'];
- var fields = bin_fields.hasOwnProperty(type) ? bin_fields[type] : [];
- for (var i = 0; i < users.length; i++) {
- var pre = document.getElementById('sudo_config_' + users[i].replace(/-/g, '_'));
- pre.textContent = 'Defaults:' + users[i] + ' !requiretty' + "\n";
- for (var j = 0; j < fields.length; j++) {
- val = document.getElementById(fields[j]).value.trim();
- if (val) {
- pre.textContent += users[i] + ' ALL = (root) NOPASSWD: ' + val + "\n";
- }
- }
- }
- $('#<%=$this->SudoConfigPopup->ClientID%>').dialog('open');
-}
+var bin_fields = {
+ bconsole: [
+ '<%=$this->BconsolePath->ClientID%>'
+ ],
+ config: [
+ '<%=$this->BDirJSONPath->ClientID%>',
+ '<%=$this->BSdJSONPath->ClientID%>',
+ '<%=$this->BFdJSONPath->ClientID%>',
+ '<%=$this->BBconsJSONPath->ClientID%>',
+ ],
+ actions: [
+ '<%=$this->DirStartAction->ClientID%>',
+ '<%=$this->DirStopAction->ClientID%>',
+ '<%=$this->DirRestartAction->ClientID%>',
+ '<%=$this->SdStartAction->ClientID%>',
+ '<%=$this->SdStopAction->ClientID%>',
+ '<%=$this->SdRestartAction->ClientID%>',
+ '<%=$this->FdStartAction->ClientID%>',
+ '<%=$this->FdStopAction->ClientID%>',
+ '<%=$this->FdRestartAction->ClientID%>'
+ ]
+};
+oSudoConfig.set_bin_fields(bin_fields);
var bjsontools_fields = {
General: {
<module id="json_tools" class="Application.API.Class.JSONTools" />
<!-- config modules -->
<module id="api_config" class="Application.API.Class.APIConfig" />
+ <module id="device_config" class="Application.API.Class.DeviceConfig" />
+ <module id="bacula_setting" class="Application.API.Class.BaculaSetting" />
<!-- internalization modules -->
<module id="globalization" class="TGlobalization">
<translation type="gettext" source="Application.API.Lang" marker="@@" autosave="false" cache="false" DefaultCulture="en" />
<module id="oauth2_config" class="Application.API.Class.OAuth2.OAuth2Config" />
<!-- component actions modules -->
<module id="comp_actions" class="Application.API.Class.ComponentActions" />
+ <!-- changer command modules -->
+ <module id="changer_command" class="Application.API.Class.ChangerCommand" />
</modules>
</configuration>
<url ServiceParameter="APIInstallWizard" pattern="panel/config/" />
<url ServiceParameter="APIBasicUsers" pattern="panel/basic/" />
<url ServiceParameter="APIOAuth2Clients" pattern="panel/oauth2/" />
+ <url ServiceParameter="APIDevices" pattern="panel/devices/" />
<url ServiceParameter="APISettings" pattern="panel/settings/" />
</urls>
<a href="<%=$this->Service->constructUrl('APIHome')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIHome' ? ' w3-blue': ''%>"><i class="fas fa-tachometer-alt fa-fw"></i> <%[ Dashboard ]%></a>
<a href="<%=$this->Service->constructUrl('APIBasicUsers')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIBasicUsers' ? ' w3-blue': ''%>"><i class="fa fa-users fa-fw"></i> <%[ Basic users ]%></a>
<a href="<%=$this->Service->constructUrl('APIOAuth2Clients')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIOAuth2Clients' ? ' w3-blue': ''%>"><i class="fa fa-user-shield fa-fw"></i> <%[ OAuth2 clients ]%></a>
+ <a href="<%=$this->Service->constructUrl('APIDevices')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIDevices' ? ' w3-blue': ''%>"><i class="fa fa-server fa-fw"></i> <%[ Devices ]%></a>
<a href="<%=$this->Service->constructUrl('APISettings')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APISettings' ? ' w3-blue': ''%>"><i class="fa fa-wrench fa-fw"></i> <%[ Settings ]%></a>
<a href="<%=$this->Service->constructUrl('APIInstallWizard')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIInstallWizard' ? ' w3-blue': ''%>"><i class="fa fa-hat-wizard fa-fw"></i> <%[ Configuration wizard ]%></a>
</div>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 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('System.Web.UI.JuiControls.TJuiDialog');
+Prado::using('Application.Common.Portlets.PortletTemplate');
+
+/**
+ * Sudo config control.
+ * It displays dialog window with sudo config for specific operating systems.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Control
+ * @package Baculum API
+ */
+class SudoConfig extends PortletTemplate {
+}
+?>
--- /dev/null
+<script>
+var oSudoConfig = {
+ bin_fields: {},
+ bin_opts: {},
+ ids: {
+ dialog: '<%=$this->SudoConfigPopup->ClientID%>'
+ },
+ set_bin_fields: function(bin_fields) {
+ this.bin_fields = bin_fields;
+ },
+ set_bin_opts: function(bin_opts) {
+ this.bin_opts = bin_opts;
+ },
+ get_config: function(type) {
+ var val, pre;
+ var cfg = '';
+ var users = ['apache', 'lighttpd', 'www-data'];
+ var fields = this.bin_fields.hasOwnProperty(type) ? this.bin_fields[type] : [];
+ for (var i = 0; i < users.length; i++) {
+ var pre = document.getElementById('sudo_config_' + users[i].replace(/-/g, '_'));
+ pre.textContent = 'Defaults:' + users[i] + ' !requiretty' + "\n";
+ for (var j = 0; j < fields.length; j++) {
+ val = document.getElementById(fields[j]).value.trim();
+ if (this.bin_opts.hasOwnProperty(type)) {
+ if (this.bin_opts[type].hasOwnProperty('base_path') && this.bin_opts[type].base_path) {
+ val = val.split(' ').shift(); // NOTE: It will not work with paths containing spaces
+ }
+ }
+ if (val) {
+ pre.textContent += users[i] + ' ALL = (root) NOPASSWD: ' + val + "\n";
+ }
+ }
+ }
+ $('#' + this.ids.dialog).dialog('open');
+ }
+};
+</script>
+<com:TJuiDialog
+ ID="SudoConfigPopup"
+ Options.title="<%[ Sudo configuration ]%>"
+ Options.autoOpen="false",
+ Options.minWidth="820"
+ Options.minHeight="200"
+>
+ <p><%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%></p>
+ <p><strong><%[ Note ]%></strong> <%[ Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file. ]%>
+ <p><%[ Example sudo configuration for Apache web server user (RHEL, CentOS and others): ]%></p>
+ <pre id="sudo_config_apache"></pre>
+ <p><%[ Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others): ]%></p>
+ <pre id="sudo_config_lighttpd"></pre>
+ <p><%[ Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others): ]%></p>
+ <pre id="sudo_config_www_data"></pre>
+</com:TJuiDialog>
*/
class BClientScript extends TClientScript {
- const SCRIPTS_VERSION = 17;
+ const SCRIPTS_VERSION = 18;
public function getScriptUrl()
{
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.';
+}
+
?>
/**
* 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.
'directors',
'clients',
'storages',
+ 'devices',
'volumes',
'pools',
'bvfs',
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();
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;
}
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.
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);
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"
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"
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"
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"
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"
Visible="<%=!empty($_SESSION['sd']) && $this->getIsAutochanger()%>"
OnClick="setAutochanger"
/>
+ <com:TActiveLinkButton
+ CssClass="w3-bar-item w3-button tab_btn"
+ Attributes.onclick="W3Tabs.open(this.id, 'manage_autochanger');"
+ Text="<%[ Manage autochanger ]%>"
+ Visible="<%=!empty($_SESSION['sd']) && $this->getIsAutochanger()%>"
+ OnCallback="loadAutochanger"
+ />
</div>
<div class="w3-container tab_item" id="storage_actions">
<com:TValidationSummary
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);"
>
<prop:Text><%=Prado::localize('Mount')%> <i class="fa fa-caret-down"></i></prop:Text>
</com:TActiveLinkButton>
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);"
>
<prop:Text><%=Prado::localize('Release')%> <i class="fa fa-caret-right"></i></prop:Text>
</com:TActiveLinkButton>
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);"
>
<prop:Text><%=Prado::localize('Umount')%> <i class="fa fa-caret-up"></i></prop:Text>
</com:TActiveLinkButton>
+ <com:TCallback ID="MountLoading" OnCallback="mountLoading" />
+ <com:TCallback ID="ReleaseLoading" OnCallback="releaseLoading" />
+ <com:TCallback ID="UmountLoading" OnCallback="umountLoading" />
+ <script>
+var oStorageActions = {
+ ids: {
+ log: 'storage_action_log',
+ log_container: 'storage_action_text_output',
+ loader: 'status_storage_loading'
+ },
+ refresh_mount: function(out_id) {
+ setTimeout(function() {
+ var cb = <%=$this->MountLoading->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ }, 2000);
+ },
+ refresh_release: function(out_id) {
+ setTimeout(function() {
+ var cb = <%=$this->ReleaseLoading->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ }, 2000);
+ },
+ refresh_umount: function(out_id) {
+ setTimeout(function() {
+ var cb = <%=$this->UmountLoading->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ }, 2000);
+ },
+ log: function(output) {
+ var log = document.getElementById(oStorageActions.ids.log);
+ log.textContent = output;
+ },
+ clear_log: function() {
+ document.getElementById(oStorageActions.ids.log_container).style.display = 'none';
+ oStorageActions.log('');
+ },
+ show_loader: function(show) {
+ var loader = document.getElementById(oStorageActions.ids.loader);
+ if (show) {
+ oStorageActions.clear_log();
+ $('#' + oStorageActions.ids.log_container).slideDown();
+ loader.style.display = '';
+ } else {
+ loader.style.display = 'none';
+ }
+ }
+};
+ </script>
<i id="status_storage_loading" class="fa fa-sync w3-spin" style="display: none; vertical-align: super;"></i> <span id="status_storage_error" class="w3-text-red" style="display: none"></span>
<com:TActivePanel ID="Autochanger" Display="None" Height="61px">
<div class="w3-left w3-margin-right">
- <label><%[ Drive number: ]%></label>
- <com:TActiveTextBox ID="Drive" AutoPostBack="false" Text="0" MaxLength="3" CssClass="w3-input smallbox" />
- <com:TDataTypeValidator ID="DriveValidator" ValidationGroup="AutoChangerGroup" ControlToValidate="Drive" ErrorMessage="<%[ Drive number must be integer. ]%>" Display="None" DataType="Integer" />
+ <label><%[ Drive number: ]%></label>
+ <com:TActiveTextBox
+ ID="Drive"
+ AutoPostBack="false"
+ Text="0"
+ MaxLength="3"
+ CssClass="w3-input smallbox"
+ />
+ <com:TDataTypeValidator
+ ID="DriveValidator"
+ ValidationGroup="AutoChangerGroup"
+ ControlToValidate="Drive"
+ ErrorMessage="<%[ Drive number must be integer. ]%>"
+ Display="None"
+ DataType="Integer"
+ />
</div>
<div class="w3-left">
<%[ Slot number: ]%>
- <com:TActiveTextBox ID="Slot" AutoPostBack="false" Text="0" MaxLength="3" CssClass="w3-input smallbox" />
- <com:TDataTypeValidator ID="SlotValidator" ValidationGroup="AutoChangerGroup" ControlToValidate="Slot" ErrorMessage="<%[ Slot number must be integer. ]%>" Display="None" DataType="Integer" />
+ <com:TActiveTextBox
+ ID="Slot"
+ AutoPostBack="false"
+ Text="0"
+ MaxLength="3"
+ CssClass="w3-input smallbox"
+ />
+ <com:TDataTypeValidator
+ ID="SlotValidator"
+ ValidationGroup="AutoChangerGroup"
+ ControlToValidate="Slot"
+ ErrorMessage="<%[ Slot number must be integer. ]%>"
+ Display="None"
+ DataType="Integer"
+ />
</div>
</com:TActivePanel>
<div id="storage_action_text_output" class="w3-code" style="display: none; clear: both;">
- <pre><com:TActiveLabel ID="StorageActionLog" /></pre>
+ <pre id="storage_action_log"></pre>
</div>
<div id="status_storage_container" class="w3-clear w3-margin-top" style="display: none">
<div class="w3-right w3-margin-top w3-margin-right" title="<%[ To disable refreshing please type 0. ]%>">
ShowCancelButton="false"
/>
</div>
+ <div class="w3-container tab_item" id="manage_autochanger" style="display: none">
+ <div id="manage_autochanger_not_available">
+ <strong><%[ Autochanger management is unavailable. To manage autochanger from here, add it to the API host devices on the API host side. ]%></strong>
+ </div>
+ <div id="drive_list_container">
+ <h5><%[ Tape drives ]%></h5>
+ <table id="drive_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+ <thead>
+ <tr>
+ <th></th>
+ <th><%[ Drive index ]%></th>
+ <th><%[ Drive name ]%></th>
+ <th><%[ Device ]%></th>
+ <th><%[ Volume ]%></th>
+ <th><%[ Slot ]%></th>
+ <th><%[ Actions ]%></th>
+ </tr>
+ </thead>
+ <tbody id="drive_list_body"></tbody>
+ <tfoot>
+ <tr>
+ <th></th>
+ <th><%[ Drive index ]%></th>
+ <th><%[ Drive name ]%></th>
+ <th><%[ Device ]%></th>
+ <th><%[ Volume ]%></th>
+ <th><%[ Slot ]%></th>
+ <th><%[ Actions ]%></th>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ <com:TCallback ID="UnloadDrive" OnCallback="unloadDrive" />
+ <com:TCallback ID="UnloadedDrive" OnCallback="unloadedDrive" />
+ <script>
+var oDriveList = {
+ ids: {
+ drive_list: 'drive_list',
+ drive_list_body: 'drive_list_body'
+ },
+ table: null,
+ data: [],
+ init: function() {
+ if (!this.table) {
+ this.set_table();
+ } else {
+ var page = this.table.page();
+ this.table.clear().rows.add(this.data).draw();
+ this.table.page(page).draw(false);
+ }
+ },
+ set_table: function() {
+ this.table = $('#' + this.ids.drive_list).DataTable({
+ data: this.data,
+ deferRender: true,
+ dom: 'lBfrtip',
+ stateSave: true,
+ buttons: [
+ 'copy', 'csv', 'colvis'
+ ],
+ columns: [
+ {
+ className: 'details-control',
+ orderable: false,
+ data: null,
+ defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+ },
+ {data: 'index'},
+ {data: 'drive'},
+ {data: 'device'},
+ {
+ data: 'volume',
+ render: function(data, type, row) {
+ var v = data;
+ if (type == 'display' && row.mediaid > 0) {
+ var a = document.createElement('A');
+ a.href = '/web/volume/' + row.mediaid + '/';
+ var icon = document.createElement('I');
+ icon.className = 'fa fa-external-link-alt fa-xs';
+ a.appendChild(icon);
+ v += ' ' + a.outerHTML;
+ }
+ return v;
+ }
+ },
+ {data: 'slot_ach'},
+ {
+ data: 'slot_ach',
+ render: function(data, type, row) {
+ var btn = document.createElement('BUTTON');
+ btn.className = 'w3-button w3-green';
+ btn.type = 'button';
+ var i = document.createElement('I');
+ i.className = 'fa fa-upload';
+ var label = document.createTextNode(' <%[ Unload ]%>');
+ btn.appendChild(i);
+ btn.innerHTML += ' ';
+ btn.appendChild(label);
+ btn.setAttribute('onclick', 'oDrives.unload_drive("' + row.drive + '", ' + data + ')');
+ if (data === '') {
+ btn.style.visibility = 'hidden';
+ }
+ return btn.outerHTML;
+ }
+ }
+ ],
+ responsive: {
+ details: {
+ type: 'column'
+ }
+ },
+ columnDefs: [{
+ className: 'control',
+ orderable: false,
+ targets: 0
+ },
+ {
+ className: "dt-center",
+ targets: [ 1, 5, 6 ]
+ }],
+ order: [1, 'asc']
+ });
+ }
+};
+
+var oDrives = {
+ unload_drive_timeout: 300000, // 5 minutes
+ load_drives_cb: function(data) {
+ oDriveList.data = data;
+ oDriveList.init();
+ },
+ unload_drive: function(drive, slot) {
+ var cb = <%=$this->UnloadDrive->ActiveControl->Javascript%>;
+ var param = {
+ drive: drive,
+ slot: slot
+ };
+ cb.setCallbackParameter(param);
+ cb.setRequestTimeOut(this.unload_drive_timeout);
+ cb.dispatch();
+ oSlots.show_changer_loader(true);
+ },
+ set_drive_unloading_output: function(out_id) {
+ var cb = <%=$this->UnloadedDrive->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ },
+ refresh_drive_unloading: function(out_id) {
+ setTimeout(function() {
+ oDrives.set_drive_unloading_output(out_id);
+ }, 2000)
+ }
+};
+ </script>
+ <div id="slot_list_container">
+ <h5 class="w3-show-inline-block"><%[ Changer slots ]%> <i id="changer_loader" class="fas fa-sync-alt w3-spin" style="display: none"></i></h5>
+ <p class="info w3-right"><%[ Tip: To use bulk autochanger actions, please select table rows. ]%></p>
+ <button type="button" id="release_all_ie_btn" class="w3-button w3-green" style="display: none" onclick="oSlots.release_all_ie();">
+ <i class="fas fa-share-square"></i> <%[ Release all I/E slots ]%>
+ </button>
+ <table id="slot_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+ <thead>
+ <tr>
+ <th></th>
+ <th><%[ Slot in device ]%></th>
+ <th><%[ Slot in catalog ]%></th>
+ <th><%[ Volume ]%></th>
+ <th><%[ Vol. status ]%></th>
+ <th><%[ Vol. bytes ]%></th>
+ <th><%[ Pool ]%></th>
+ <th>MediaType</th>
+ <th><%[ Last written ]%></th>
+ <th><%[ When expire ]%></th>
+ <th><%[ Actions ]%></th>
+ </tr>
+ </thead>
+ <tbody id="slot_list_body"></tbody>
+ <tfoot>
+ <tr>
+ <th></th>
+ <th><%[ Slot in device ]%></th>
+ <th><%[ Slot in catalog ]%></th>
+ <th><%[ Volume ]%></th>
+ <th><%[ Vol. status ]%></th>
+ <th><%[ Vol. bytes ]%></th>
+ <th><%[ Pool ]%></th>
+ <th>MediaType</th>
+ <th><%[ Last written ]%></th>
+ <th><%[ When expire ]%></th>
+ <th><%[ Actions ]%></th>
+ </tr>
+ </tfoot>
+ </table>
+ <p class="info w3-hide-medium w3-hide-small"><%[ Tip: Use left-click to select table row. Use CTRL + left-click to multiple row selection. Use SHIFT + left-click to add a range of rows to selection. ]%></p>
+ </div>
+ <com:TCallback ID="LabelUsingBarcodesAction" OnCallback="labelBarcodes" />
+ <com:TCallback ID="UpdateSlotsBarcodesAction" OnCallback="updateSlotsBarcodes" />
+ <com:TCallback ID="UpdateSlotsAction" OnCallback="updateSlots" />
+ <com:TCallback ID="MoveToImportExportSlot" OnCallback="moveToIE" />
+ <com:TCallback ID="MovingToImportExportSlot" OnCallback="movingToIE" />
+ <com:TCallback ID="MoveFromImportExportSlot" OnCallback="moveFromIE" />
+ <com:TCallback ID="MovingFromImportExportSlot" OnCallback="movingFromIE" />
+ <com:TCallback ID="ReleaseImportExportSlot" OnCallback="releaseIE" />
+ <com:TCallback ID="ReleasingImportExportSlot" OnCallback="releasingIE" />
+ <com:Application.Web.Portlets.LabelVolume
+ ID="LabelBarcodes"
+ ShowButton="false"
+ BarcodeLabel="true"
+ Storage="<%=$this->StorageName%>"
+ OnLabelStart="showChangerLoading"
+ OnLabelComplete="loadAutochanger"
+ OnLabelSuccess="hideChangerLoading"
+ OnLabelFail="hideChangerLoading"
+ />
+ <com:Application.Web.Portlets.UpdateSlots
+ ID="UpdateSlots"
+ ShowButton="false"
+ Storage="<%=$this->StorageName%>"
+ OnUpdateStart="showChangerLoading"
+ OnUpdateComplete="loadAutochanger"
+ OnUpdateSuccess="hideChangerLoading"
+ OnUpdateFail="hideChangerLoading"
+ />
+ <com:TActiveHiddenField ID="IESlots" />
+ <script>
+var oSlotList = {
+ table_toolbar: null,
+ actions: [
+ {
+ action: 'label',
+ label: '<%[ Label using barcodes ]%>',
+ value: 'slot_ach',
+ callback: <%=$this->LabelUsingBarcodesAction->ActiveControl->Javascript%>,
+ before: function() {
+ show_label_volume_window(true);
+ }
+ },
+ {
+ action: 'move_to_ie',
+ label: '<%[ Move to import/export slot ]%>',
+ value: 'slot_ach',
+ before: function() {
+ oSlots.show_move_to_ie_window(true);
+ },
+ validate: function(selected) {
+ oSlots.set_move_to_ie_slots();
+ var ie_slots = oSlots.get_slots(['ie_slot'], ['E']);
+ var ie_slots_count = ie_slots.length;
+ var vols_count = oSlots.move_to_ie_slots.length;
+ if (vols_count > ie_slots_count) {
+ var emsg = '<%[ There are to few import/export slots to transfer selected volumes. Free import/export slots count: %slots_count, selected volumes count: %vols_count. ]%>';
+ emsg = emsg.replace('%slots_count', ie_slots_count);
+ emsg = emsg.replace('%vols_count', vols_count);
+ oBulkActionsModal.set_error(emsg);
+ return false;
+ }
+ return true;
+ }
+ },
+ {
+ action: 'update_barcodes',
+ label: '<%[ Update slots using barcodes ]%>',
+ value: 'slot_ach',
+ callback: <%=$this->UpdateSlotsBarcodesAction->ActiveControl->Javascript%>,
+ before: function() {
+ set_update_slots_barcodes(true);
+ show_update_slots_window(true);
+ }
+ },
+ {
+ action: 'update',
+ label: '<%[ Update slots ]%>',
+ value: 'slot_ach',
+ callback: <%=$this->UpdateSlotsAction->ActiveControl->Javascript%>,
+ before: function() {
+ set_update_slots(true);
+ show_update_slots_window(true);
+ }
+ }
+ ],
+ ids: {
+ slot_list: 'slot_list',
+ slot_list_body: 'slot_list_body',
+ release_all_ie_btn: 'release_all_ie_btn'
+ },
+ table: null,
+ data: [],
+ init: function() {
+ if (!this.table) {
+ this.set_table();
+ this.set_bulk_actions();
+ this.init_release_all_ie_btn();
+ this.set_events();
+ } else {
+ var page = this.table.page();
+ this.table.clear().rows.add(this.data).draw();
+ this.table.page(page).draw(false);
+ }
+ oSlots.init();
+ this.set_release_all_ie_btn();
+ },
+ set_events: function() {
+ document.getElementById(this.ids.slot_list).addEventListener('click', function(e) {
+ $(function() {
+ this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none';
+ }.bind(this));
+ }.bind(this));
+ },
+ set_table: function() {
+ this.table = $('#' + this.ids.slot_list).DataTable({
+ data: this.data,
+ pageLength: 100,
+ deferRender: true,
+ dom: 'lB<"table_toolbar">frtip',
+ stateSave: true,
+ buttons: [
+ 'copy', 'csv', 'colvis'
+ ],
+ columns: [
+ {
+ className: 'details-control',
+ orderable: false,
+ data: null,
+ defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+ },
+ {data: 'slot_ach'},
+ {data: 'slot_cat'},
+ {
+ data: 'volume',
+ render: function(data, type, row) {
+ var v = data;
+ var link = '';
+ if (!row.slot_cat) {
+ for (var i = 0; i < oDriveList.data.length; i++) {
+ if (row.slot_ach === oDriveList.data[i].slot_ach) {
+ v = '<%[ In drive %index (%drive) ]%>';
+ v = v.replace('%index', oDriveList.data[i].index);
+ v = v.replace('%drive', oDriveList.data[i].drive);
+ break;
+ }
+ }
+ }
+ if (type == 'display' && row.mediaid > 0) {
+ var a = document.createElement('A');
+ a.href = '/web/volume/' + row.mediaid + '/';
+ var icon = document.createElement('I');
+ icon.className = 'fa fa-external-link-alt fa-xs';
+ a.appendChild(icon);
+ link = ' ' + a.outerHTML;
+ }
+ if (!data && !v) {
+ if (row.state == 'F') {
+ v = '<%[ Full ]%>';
+ } else if (row.state == 'E') {
+ v = '<%[ Empty ]%>';
+ }
+ }
+ return (v + link);
+ }
+ },
+ {data: 'volstatus'},
+ {
+ data: 'volbytes',
+ render: render_bytes
+ },
+ {data: 'pool'},
+ {data: 'mediatype'},
+ {
+ data: 'lastwritten',
+ render: render_date
+ },
+ {
+ data: 'whenexpire',
+ render: render_date_ex
+ },
+ {
+ data: 'slot_ach',
+ render: function(data, type, row) {
+ var ret = '';
+ if (row.type == 'ie_slot') {
+ if (row.state === 'F') {
+ var clear_io = document.createElement('BUTTON');
+ clear_io.className = 'w3-button w3-green';
+ clear_io.type = 'button';
+ var i = document.createElement('I');
+ i.className = 'fa fa-sign-out-alt';
+ var label = document.createTextNode(' <%[ Release I/E ]%>');
+ clear_io.appendChild(i);
+ clear_io.innerHTML += ' ';
+ clear_io.appendChild(label);
+ clear_io.setAttribute('onclick', 'oSlots.release_ie_window(' + data + ');');
+ ret = clear_io.outerHTML;
+ } else {
+ ret = '<%[ Import/Export ]%>';
+ }
+ } else {
+ var load_btn = document.createElement('BUTTON');
+ load_btn.className = 'w3-button w3-green';
+ load_btn.type = 'button';
+ var i = document.createElement('I');
+ i.className = 'fa fa-download';
+ var label = document.createTextNode(' <%[ Load ]%>');
+ load_btn.appendChild(i);
+ load_btn.innerHTML += ' ';
+ load_btn.appendChild(label);
+ load_btn.setAttribute('onclick', 'oSlots.load_drive_window(' + data + ');');
+ if (row.state === 'E') {
+ load_btn.style.visibility = 'hidden';
+ }
+ ret = load_btn.outerHTML;
+ }
+ return ret;
+ }
+ }
+ ],
+ responsive: {
+ details: {
+ type: 'column'
+ }
+ },
+ columnDefs: [{
+ className: 'control',
+ orderable: false,
+ targets: 0
+ },
+ {
+ className: "dt-right",
+ targets: [ 5 ]
+ },
+ {
+ className: "dt-center",
+ targets: [ 1, 2, 4 ]
+ }],
+ select: {
+ style: 'os',
+ selector: 'td:not(:last-child):not(:first-child)',
+ blurable: false
+ },
+ order: [1, 'asc']
+ });
+ },
+ set_bulk_actions: function() {
+ this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+ actions: '<%[ Actions ]%>',
+ ok: '<%[ OK ]%>'
+ });
+ },
+ init_release_all_ie_btn: function() {
+ var btn = document.getElementById(this.ids.release_all_ie_btn);
+ var toolbar = document.querySelector('#' + this.ids.slot_list + '_wrapper div.dt-buttons');
+ toolbar.appendChild(btn);
+ },
+ show_release_all_ie_btn: function(show) {
+ var btn = document.getElementById(this.ids.release_all_ie_btn);
+ if (show) {
+ btn.style.display = '';
+ } else {
+ btn.style.display = 'none';
+ }
+ },
+ set_release_all_ie_btn: function() {
+ var full = oSlots.get_slots(['ie_slot'], ['F']);
+ if (full.length > 0) {
+ this.show_release_all_ie_btn(true);
+ } else {
+ this.show_release_all_ie_btn(false);
+ }
+ }
+};
+
+var oSlots = {
+ ids: {
+ load_drive_window: 'load_drive_window',
+ load_drive_drives: 'load_drive_drives',
+ load_drive_out: 'load_drive_out',
+ load_drive_loading: 'load_drive_loading',
+ load_drive_slot: 'load_drive_slot',
+ move_to_ie_window: 'move_to_ie_window',
+ move_to_ie_slots: 'move_to_ie_slots',
+ move_to_ie_window_log: 'move_to_ie_window_log',
+ move_to_ie_loading: 'move_to_ie_loading',
+ release_ie_window: 'release_ie_window',
+ release_ie_slot: 'release_ie_slot',
+ release_ie_loading: 'release_ie_loading',
+ release_ie_window_log: 'release_ie_window_log',
+ release_ie_dest_slot: 'release_ie_dest_slot',
+ changer_loader: 'changer_loader'
+ },
+ load_drive_timeout: 300000, // 5 minutes
+ move_to_ie_timeout: 300000, // 5 minutes
+ move_from_ie_timeout: 300000, // 5 minutes
+ release_ie_timeout: 300000, // 5 minutes
+ slot: null,
+ ie_slot: null,
+ empty_slots: [],
+ full_slots: [],
+ empty_ie_slots: [],
+ full_ie_slots: [],
+ move_to_ie_slots: [],
+ free_ie_slots: [],
+ init: function() {
+ this.set_slots();
+ },
+ set_slots: function() {
+ this.empty_slots = this.get_slots(['slot'], ['E']);
+ this.full_slots = this.get_slots(['slot'], ['F']);
+ this.empty_ie_slots = this.get_slots(['ie_slot'], ['F']);
+ this.full_ie_slots = this.get_slots(['ie_slot'], ['F']);
+ },
+ load_slots_cb: function(data) {
+ oSlotList.data = data;
+ oSlotList.init();
+ },
+ load_drive_window: function(slot) {
+ this.slot = slot;
+ this.prepare_drive_list();
+ this.show_load_drive_window(true);
+ },
+ prepare_drive_list: function() {
+ var sel = document.getElementById(this.ids.load_drive_drives);
+ while(sel.firstChild) {
+ sel.removeChild(sel.firstChild);
+ }
+ var opt, text, label, state;
+ for (var i = 0; i < oDriveList.data.length; i++) {
+ opt = document.createElement('OPTION');
+ opt.value = oDriveList.data[i].drive;
+ if (oDriveList.data[i].state === 'F') {
+ opt.setAttribute('disabled', 'disabled');
+ state = '[<%[ Full ]%>] ';
+ } else if (oDriveList.data[i].state === 'E') {
+ state = '[<%[ Empty ]%>] ';
+ }
+ text = state + oDriveList.data[i].drive + ' (<%[ index ]%>: ' + oDriveList.data[i].index + ', <%[ device ]%>: ' + oDriveList.data[i].device + ')';
+ label = document.createTextNode(text);
+ opt.appendChild(label);
+ sel.appendChild(opt);
+ }
+ },
+ show_load_drive_window: function(show) {
+ this.set_load_drive_window_slot();
+ var win = document.getElementById(oSlots.ids.load_drive_window);
+ if (show) {
+ win.style.display = 'block';
+ } else {
+ win.style.display = 'none';
+ }
+ },
+ set_load_drive_window_slot: function() {
+ document.getElementById(this.ids.load_drive_slot).textContent = this.slot;
+ },
+ load_drive_loading: function(show) {
+ var loader = document.getElementById(oSlots.ids.load_drive_loading);
+ if (show) {
+ loader.style.visibility = 'visible';
+ } else {
+ loader.style.visibility = 'hidden';
+ }
+ },
+ set_drive_window_ok: function() {
+ oSlots.load_drive_loading(false);
+ oSlots.show_load_drive_window(false);
+ },
+ load_drive: function() {
+ var sel = document.getElementById(this.ids.load_drive_drives);
+ var cb = <%=$this->LoadDrive->ActiveControl->Javascript%>;
+ if (!sel.value) {
+ // no free tape drive available
+ return false;
+ }
+ var param = {
+ drive: sel.value,
+ slot: this.slot
+ };
+ cb.setCallbackParameter(param);
+ cb.setRequestTimeOut(this.load_drive_timeout);
+ cb.dispatch();
+ this.show_changer_loader(true);
+ this.load_drive_loading(true);
+ },
+ set_drive_with_mount_loading_output: function(out_id) {
+ var cb = <%=$this->LoadedDriveWithMount->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ },
+ refresh_drive_with_mount_loading: function(out_id) {
+ setTimeout(function() {
+ oSlots.set_drive_with_mount_loading_output(out_id);
+ }, 2000)
+ },
+ set_drive_without_mount_loading_output: function(out_id) {
+ var cb = <%=$this->LoadedDriveWithoutMount->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ },
+ refresh_drive_without_mount_loading: function(out_id) {
+ setTimeout(function() {
+ oSlots.set_drive_without_mount_loading_output(out_id);
+ }, 2000)
+ },
+ get_slots: function(types, states) {
+ if (!states) {
+ states = ['E', 'F'];
+ }
+ if (!types) {
+ types = ['slot', 'ie_slot']
+ }
+ var ie_slots = [];
+ for (var i = 0; i < oSlotList.data.length; i++) {
+ if (types.indexOf(oSlotList.data[i].type) != -1 && states.indexOf(oSlotList.data[i].state) != -1) {
+ ie_slots.push(oSlotList.data[i].slot_ach);
+ }
+ }
+ return ie_slots;
+ },
+ set_ie_slots: function() {
+ this.free_ie_slots = oSlots.get_slots(['ie_slot'], ['E']);
+ },
+ show_move_to_ie_window: function(show) {
+ this.set_ie_slots();
+ this.set_move_to_ie_slots();
+ this.move_to_ie_loading(false);
+ var win = document.getElementById(oSlots.ids.move_to_ie_window);
+ if (show) {
+ win.style.display = 'block';
+ } else {
+ win.style.display = 'none';
+ }
+ },
+ set_move_to_ie_slots: function() {
+ var slots = [];
+ var selected = oSlotList.table.rows({selected: true}).data();
+ for (var i = 0; i < selected.length; i++) {
+ if (selected[i].state == 'F') {
+ slots.push(selected[i].slot_ach);
+ }
+ }
+ this.move_to_ie_slots = slots;
+ document.getElementById(this.ids.move_to_ie_slots).textContent = slots.join(',');
+ },
+ move_to_ie_loading: function(show) {
+ var loader = document.getElementById(oSlots.ids.move_to_ie_loading);
+ if (show) {
+ loader.style.visibility = 'visible';
+ } else {
+ loader.style.visibility = 'hidden';
+ }
+ },
+ move_to_ie: function() {
+ var slot = oSlots.move_to_ie_slots.shift();
+ var ie_slot = oSlots.free_ie_slots.shift();
+ if (slot && ie_slot) {
+ oSlots.move_to_ie_loading(true);
+ var param = [slot, ie_slot].join(',');
+ var cb = <%=$this->MoveToImportExportSlot->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(param);
+ cb.RequestTimeOut = oSlots.move_to_ie_timeout;
+ cb.dispatch();
+ oSlots.show_changer_loader(true);
+ } else if (!slot) {
+ oSlots.show_move_to_ie_window(false);
+ oSlotList.set_release_all_ie_btn();
+ } else if (!ie_slot) {
+ // There is not enough import/export slots to transfer all selected tapes.
+ // It shouldn't happen.
+ }
+ },
+ set_move_to_ie_log: function(log) {
+ var logbox = document.getElementById(oSlots.ids.move_to_ie_window_log);
+ if (log && logbox.style.display == 'none') {
+ logbox.style.display = 'block';
+ }
+ logbox.getElementsByTagName('PRE')[0].textContent = log;
+ },
+ set_move_to_ie_output: function(out_id) {
+ var cb = <%=$this->MovingToImportExportSlot->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ },
+ refresh_move_to_ie_loading: function(out_id) {
+ setTimeout(function() {
+ oSlots.set_move_to_ie_output(out_id);
+ }, 2000)
+ },
+ show_release_ie_window: function(show) {
+ var win = document.getElementById(oSlots.ids.release_ie_window);
+ if (show) {
+ oSlots.release_ie_loading(false);
+ oSlots.set_release_ie_log('');
+ win.style.display = 'block';
+ } else {
+ win.style.display = 'none';
+ }
+ },
+ set_release_ie_window_slot: function() {
+ document.getElementById(this.ids.release_ie_slot).textContent = this.ie_slot;
+ },
+ release_ie_loading: function(show) {
+ var loader = document.getElementById(oSlots.ids.release_ie_loading);
+ if (show) {
+ loader.style.visibility = 'visible';
+ } else {
+ loader.style.visibility = 'hidden';
+ }
+ },
+ release_ie_window: function(slot) {
+ this.ie_slot = slot;
+ this.prepare_release_ie();
+ this.set_release_ie_window_slot();
+ this.show_release_ie_window(true);
+ },
+ prepare_release_ie: function() {
+ var sel = document.getElementById(this.ids.release_ie_dest_slot);
+ while(sel.firstChild) {
+ sel.removeChild(sel.firstChild);
+ }
+ var opt, text, label, state;
+ for (var i = 0; i < oSlotList.data.length; i++) {
+ opt = document.createElement('OPTION');
+ opt.value = oSlotList.data[i].slot_ach;
+ if (oSlotList.data[i].state === 'F') {
+ opt.setAttribute('disabled', 'disabled');
+ state = '[<%[ Full ]%>] ';
+ } else if (oSlotList.data[i].state === 'E') {
+ state = '[<%[ Empty ]%>] ';
+ }
+ text = state + '<%[ Slot ]%>: ' + oSlotList.data[i].slot_ach;
+ label = document.createTextNode(text);
+ opt.appendChild(label);
+ sel.appendChild(opt);
+ }
+ },
+ release_ie: function(finish) {
+ var sel = document.getElementById(oSlots.ids.release_ie_dest_slot);
+ if (oSlots.ie_slot && sel.value && !finish) {
+ oSlots.release_ie_loading(true);
+ var param = [oSlots.ie_slot, sel.value].join(',');
+ var cb = <%=$this->ReleaseImportExportSlot->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(param);
+ cb.RequestTimeOut = oSlots.release_ie_timeout;
+ cb.dispatch();
+ oSlots.show_changer_loader(true);
+ } else if (finish) {
+ oSlots.show_release_ie_window(false);
+ oSlotList.set_release_all_ie_btn();
+ }
+ },
+ set_release_ie_log: function(log) {
+ var logbox = document.getElementById(oSlots.ids.release_ie_window_log);
+ if (log && logbox.style.display == 'none') {
+ logbox.style.display = 'block';
+ }
+ logbox.getElementsByTagName('PRE')[0].textContent = log;
+ },
+ set_release_ie_output: function(out_id) {
+ var cb = <%=$this->ReleasingImportExportSlot->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ },
+ refresh_release_ie_loading: function(out_id) {
+ setTimeout(function() {
+ oSlots.set_release_ie_output(out_id);
+ }, 2000)
+ },
+ release_all_ie: function() {
+ var fie_len = oSlots.full_ie_slots.length;
+ var es_len = oSlots.empty_slots.length;
+ if (fie_len > es_len) {
+ var emsg = '<%[ 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. ]%>';
+ emsg = emsg.replace('%ie_slot_count', fie_len);
+ emsg = emsg.replace('%slot_count', es_len);
+ oBulkActionsModal.set_error(emsg);
+ return false;
+ }
+ var ie_slot = oSlots.full_ie_slots.shift()
+ var slot = oSlots.empty_slots.shift();
+
+ if (slot && ie_slot) {
+ var param = [ie_slot, slot].join(',');
+ var cb = <%=$this->MoveFromImportExportSlot->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(param);
+ cb.RequestTimeOut = oSlots.move_from_ie_timeout;
+ cb.dispatch();
+ oSlots.show_changer_loader(true);
+ } else if (!slot) {
+ oSlotList.set_release_all_ie_btn();
+ }
+ },
+ set_release_all_ie_output: function(out_id) {
+ var cb = <%=$this->MovingFromImportExportSlot->ActiveControl->Javascript%>;
+ cb.setCallbackParameter(out_id);
+ cb.dispatch();
+ },
+ refresh_release_all_ie_loading: function(out_id) {
+ setTimeout(function() {
+ oSlots.set_release_all_ie_output(out_id);
+ }, 2000)
+ },
+ show_changer_loader: function(show) {
+ var loader = document.getElementById(oSlots.ids.changer_loader);
+ if (show) {
+ loader.style.display = '';
+ } else {
+ loader.style.display = 'none';
+ }
+ }
+};
+ </script>
+ <com:TCallback ID="LoadDrive" OnCallback="loadDrive" />
+ <com:TCallback ID="LoadedDriveWithoutMount" OnCallback="loadedDriveWithoutMount" />
+ <com:TCallback ID="LoadedDriveWithMount" OnCallback="loadedDriveWithMount" />
+ <div id="load_drive_window" class="w3-modal">
+ <div class="w3-modal-content w3-animate-top w3-card-4">
+ <header class="w3-container w3-teal">
+ <span onclick="oSlots.show_load_drive_window(false);" class="w3-button w3-display-topright">×</span>
+ <h2><%[ Load drive ]%></h2>
+ </header>
+ <div class="w3-container w3-margin-left w3-margin-right w3-margin-top w3-text-teal">
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third">
+ <%[ Slot ]%>:
+ </div>
+ <div class="w3-col w3-half">
+ <strong id="load_drive_slot"></strong>
+ </div>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third">
+ <label for="load_drive_drives"><%[ Drive ]%>:</label>
+ </div>
+ <div class="w3-col w3-half">
+ <select id="load_drive_drives" class="w3-select w3-border"></select>
+ </div>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third">
+ <label for="load_drive_mount"><%[ Mount volume ]%>:</label>
+ </div>
+ <div class="w3-col w3-half">
+ <com:TActiveCheckBox ID="LoadDriveMount" CssClass="w3-check" Checked="true" />
+ </div>
+ </div>
+ </div>
+ <footer class="w3-container w3-center w3-padding">
+ <button type="button" class="w3-button w3-red" onclick="oSlots.show_load_drive_window(false);"><i class="fas fa-times"></i> <%[ Cancel ]%></button>
+ <button type="button" class="w3-button w3-green w3-show-inline-block" onclick="oSlots.load_drive();"><i class="fas fa-download"></i> <%[ Load drive ]%></button>
+ <i id="load_drive_loading" class="fas fa-sync-alt fa-spin" style="visibility: hidden"></i>
+ </footer>
+ </div>
+ </div>
+ <div id="move_to_ie_window" class="w3-modal">
+ <div class="w3-modal-content w3-animate-top w3-card-4">
+ <header class="w3-container w3-teal">
+ <span onclick="oSlots.show_move_to_ie_window(false);" class="w3-button w3-display-topright">×</span>
+ <h2><%[ Move to import/export slot ]%></h2>
+ </header>
+ <div class="w3-container w3-margin-left w3-margin-right w3-margin-top w3-text-teal">
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third">
+ <%[ Slots ]%>:
+ </div>
+ <div class="w3-col w3-half">
+ <strong id="move_to_ie_slots"></strong>
+ </div>
+ </div>
+ </div>
+ <footer class="w3-container w3-center w3-padding">
+ <button type="button" class="w3-button w3-red" onclick="oSlots.show_move_to_ie_window(false);"><i class="fas fa-times"></i> <%[ Cancel ]%></button>
+ <button type="button" class="w3-button w3-green w3-show-inline-block" onclick="oSlots.move_to_ie();"><i class="fas fa-download"></i> <%[ Move to import/export slots ]%></button>
+ <i id="move_to_ie_loading" class="fas fa-sync-alt fa-spin" style="visibility: hidden"></i>
+ </footer>
+ <div class="w3-margin-left w3-margin-right" style="max-height: 400px; overflow-x: auto;">
+ <div id="move_to_ie_window_log" class="w3-code" style="display: none">
+ <pre></pre>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div id="release_ie_window" class="w3-modal">
+ <div class="w3-modal-content w3-animate-top w3-card-4">
+ <header class="w3-container w3-teal">
+ <span onclick="oSlots.show_release_ie_window(false);" class="w3-button w3-display-topright">×</span>
+ <h2><%[ Release import/export slot ]%></h2>
+ </header>
+ <div class="w3-container w3-margin-left w3-margin-right w3-margin-top w3-text-teal">
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third">
+ <%[ Import/export slot ]%>:
+ </div>
+ <div class="w3-col w3-half">
+ <strong id="release_ie_slot"></strong>
+ </div>
+ </div>
+ <div class="w3-row w3-section">
+ <div class="w3-col w3-third">
+ <label for="release_ie_dest_slot"><%[ Destination slot ]%>:</label>
+ </div>
+ <div class="w3-col w3-half">
+ <select id="release_ie_dest_slot" class="w3-select w3-border"></select>
+ </div>
+ </div>
+ </div>
+ <footer class="w3-container w3-center w3-padding">
+ <button type="button" class="w3-button w3-red" onclick="oSlots.show_release_ie_window(false);"><i class="fas fa-times"></i> <%[ Cancel ]%></button>
+ <button type="button" class="w3-button w3-green w3-show-inline-block" onclick="oSlots.release_ie();"><i class="fas fa-download"></i> <%[ Release I/E ]%></button>
+ <i id="release_ie_loading" class="fas fa-sync-alt fa-spin" style="visibility: hidden"></i>
+ </footer>
+ <div class="w3-margin-left w3-margin-right" style="max-height: 400px; overflow-x: auto;">
+ <div id="release_ie_window_log" class="w3-code" style="display: none">
+ <pre></pre>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <com:Application.Web.Portlets.BulkActionsModal ID="BulkActions" />
</com:TContent>
Prado::using('System.Web.UI.ActiveControls.TActiveRepeater');
Prado::using('System.Web.UI.ActiveControls.TActiveLinkButton');
Prado::using('Application.Common.Class.Params');
+Prado::using('Application.Common.Class.Errors');
Prado::using('Application.Web.Class.BaculumWebPage');
/**
$storageshow = $this->Application->getModule('api')->get(
array('storages', $storage->storageid, 'show')
)->output;
- $this->StorageActionLog->Text = implode(PHP_EOL, $storageshow);
$this->setStorageDevice($storageshow);
$this->setDevices();
}
$this->getCallbackClient()->callClientFunction('init_graphical_storage_status', [$storage_status]);
}
+ private function actionLoading($result, $out_id, $refresh_func) {
+ $messages_log = $this->getModule('messages_log');
+ if ($result->error === 0) {
+ $rlen = count($result->output);
+ $last = $rlen > 0 ? trim($result->output[$rlen-1]) : '';
+ if ($last === 'quit') {
+ array_pop($result->output);
+ }
+ $messages_log->append($result->output);
+ if (count($result->output) > 0) {
+ // log messages
+ $output = implode('', $result->output);
+ $this->getCallbackClient()->callClientFunction(
+ 'oStorageActions.log',
+ [$output]
+ );
+ // refresh output periodically
+ $this->getCallbackClient()->callClientFunction(
+ $refresh_func,
+ [$out_id]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oStorageActions.show_loader',
+ [false]
+ );
+ }
+ } else {
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $messages_log->append($result->output);
+ $this->getCallbackClient()->callClientFunction(
+ 'oStorageActions.log',
+ [$emsg]
+ );
+ }
+ }
+
+ private function logActionError($result) {
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $messages_log->append($result->output);
+ $this->getCallbackClient()->callClientFunction(
+ 'oStorageActions.log',
+ [$emsg]
+ );
+ }
+
public function mount($sender, $param) {
$drive = $this->getIsAutochanger() ? intval($this->Drive->Text) : 0;
$slot = $this->getIsAutochanger() ? intval($this->Slot->Text) : 0;
+ $result = $this->mountAction($drive, $slot);
+ if ($result->error === 0 && count($result->output) == 1) {
+ $out = json_decode($result->output[0]);
+ if (is_object($out) && property_exists($out, 'out_id')) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oStorageActions.refresh_mount',
+ [$out->out_id]
+ );
+ }
+ } else {
+ }
+ }
+
+ private function mountAction($drive, $slot) {
$params = [
'drive' => $drive,
'slot' => $slot
];
$query = '?' . http_build_query($params);
- $mount = $this->getModule('api')->get(
- array('storages', $this->getStorageId(), 'mount', $query)
+ $result = $this->getModule('api')->set(
+ [
+ 'storages',
+ $this->getStorageId(),
+ 'mount',
+ $query
+ ]
+ );
+ return $result;
+ }
+
+ public function mountLoading($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $parameters = [
+ 'out_id' => $out_id
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->get([
+ 'storages',
+ $this->getStorageId(),
+ 'mount',
+ $query
+ ]);
+ $this->actionLoading(
+ $result,
+ $out_id,
+ 'oStorageActions.refresh_mount'
);
- if ($mount->error === 0) {
- $this->StorageActionLog->Text = implode(PHP_EOL, $mount->output);
- } else {
- $this->StorageActionLog->Text = $mount->output;
- }
}
public function umount($sender, $param) {
$drive = $this->getIsAutochanger() ? intval($this->Drive->Text) : 0;
+ $result = $this->umountAction($drive);
+ if ($result->error === 0) {
+ if (count($result->output) == 1) {
+ $out = json_decode($result->output[0]);
+ if (is_object($out) && property_exists($out, 'out_id')) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oStorageActions.refresh_umount',
+ [$out->out_id]
+ );
+ }
+ }
+ } else {
+ $this->logActionError($result);
+ }
+ }
+
+ private function umountAction($drive) {
$params = [
'drive' => $drive
];
$query = '?' . http_build_query($params);
- $umount = $this->getModule('api')->get(
- array('storages', $this->getStorageId(), 'umount', $query)
+ $result = $this->getModule('api')->set(
+ [
+ 'storages',
+ $this->getStorageId(),
+ 'umount',
+ $query
+ ]
);
- if ($umount->error === 0) {
- $this->StorageActionLog->Text = implode(PHP_EOL, $umount->output);
- } else {
- $this->StorageActionLog->Text = $umount->output;
- }
+ return $result;
+ }
+ public function umountLoading($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $parameters = [
+ 'out_id' => $out_id
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->get([
+ 'storages',
+ $this->getStorageId(),
+ 'umount',
+ $query
+ ]);
+ $this->actionLoading(
+ $result,
+ $out_id,
+ 'oStorageActions.refresh_umount'
+ );
}
public function release($sender, $param) {
$drive = $this->getIsAutochanger() ? intval($this->Drive->Text) : 0;
+ $result = $this->releaseAction($drive);
+ if ($result->error === 0) {
+ if (count($result->output) == 1) {
+ $out = json_decode($result->output[0]);
+ if (is_object($out) && property_exists($out, 'out_id')) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oStorageActions.refresh_release',
+ [$out->out_id]
+ );
+ }
+ }
+ } else {
+ $this->logActionError($result);
+ }
+ }
+
+ private function releaseAction($drive) {
$params = [
'drive' => $drive
];
$query = '?' . http_build_query($params);
- $release = $this->getModule('api')->get(
- array('storages', $this->getStorageId(), 'release', $query)
+ $result = $this->getModule('api')->set(
+ [
+ 'storages',
+ $this->getStorageId(),
+ 'release',
+ $query
+ ]
);
- if ($release->error === 0) {
- $this->StorageActionLog->Text = implode(PHP_EOL, $release->output);
- } else {
- $this->StorageActionLog->Text = $release->output;
- }
+ return $result;
}
+ public function releaseLoading($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $parameters = [
+ 'out_id' => $out_id
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->get([
+ 'storages',
+ $this->getStorageId(),
+ 'release',
+ $query
+ ]);
+ $this->actionLoading(
+ $result,
+ $out_id,
+ 'oStorageActions.refresh_release'
+ );
+ }
+
+
public function setAutochanger($sender, $param) {
if ($this->getIsAutochanger()) {
$this->AutochangerConfig->setComponentName($_SESSION['sd']);
$this->StorageConfig->setLoadValues(true);
$this->StorageConfig->raiseEvent('OnDirectiveListLoad', $this, null);
}
+
+ public function loadAutochanger($sender, $param) {
+ $result = $this->getModule('api')->get([
+ 'devices',
+ $this->getDeviceName(),
+ 'listall'
+ ]);
+ $cb = $this->getCallbackClient();
+ if ($result->error === 0) {
+ $cb->show('drive_list_container');
+ $cb->show('slot_list_container');
+ $cb->hide('manage_autochanger_not_available');
+ $cb->callClientFunction(
+ 'oDrives.load_drives_cb',
+ [$result->output->drives]
+ );
+ $slots = array_merge(
+ $result->output->slots,
+ $result->output->ie_slots
+ );
+ $cb->callClientFunction(
+ 'oSlots.load_slots_cb',
+ [$slots]
+ );
+ } elseif ($result->error === DeviceError::ERROR_DEVICE_DEVICE_CONFIG_DOES_NOT_EXIST || $result->error === DeviceError::ERROR_DEVICE_AUTOCHANGER_DOES_NOT_EXIST) {
+ $cb->hide('drive_list_container');
+ $cb->hide('slot_list_container');
+ $cb->show('manage_autochanger_not_available');
+ }
+ }
+
+ public function loadDrive($sender, $param) {
+ $data = $param->getCallbackParameter();
+ if (!is_object($data)) {
+ return;
+ }
+ $result = [];
+ if ($this->LoadDriveMount->Checked) {
+ $parameters = [
+ 'device' => $data->drive,
+ 'slot' => $data->slot
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->set([
+ 'storages',
+ $this->getStorageId(),
+ 'mount',
+ $query
+ ]);
+ } else {
+ $parameters = [
+ 'drive' => $data->drive,
+ 'slot' => $data->slot
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->set([
+ 'devices',
+ $this->getDeviceName(),
+ 'load',
+ $query
+ ]);
+ }
+ if ($result->error === 0) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.set_drive_window_ok'
+ );
+ $out_id = '';
+ if ($this->LoadDriveMount->Checked) {
+ if (count($result->output) == 1) {
+ $out = json_decode($result->output[0]);
+ if (is_object($out) && property_exists($out, 'out_id')) {
+ $out_id = $out->out_id;
+ }
+ }
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.refresh_drive_with_mount_loading',
+ [$out_id]
+ );
+ } else {
+ $out_id = $result->output->out_id;
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.refresh_drive_without_mount_loading',
+ [$out_id]
+ );
+ }
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $this->getModule('messages_log')->append([$emsg]);
+ }
+ }
+
+ public function loadedDriveWithMount($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $parameters = [
+ 'out_id' => $out_id
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->get([
+ 'storages',
+ $this->getStorageId(),
+ 'mount',
+ $query
+ ]);
+ $this->loadedDrive(
+ 'oSlots.refresh_drive_with_mount_loading',
+ $out_id,
+ $result
+ );
+ }
+
+ public function loadedDriveWithoutMount($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $parameters = [
+ 'out_id' => $out_id
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->get([
+ 'devices',
+ $this->getDeviceName(),
+ 'load',
+ $query
+ ]);
+ $this->loadedDrive(
+ 'oSlots.refresh_drive_without_mount_loading',
+ $out_id,
+ $result
+ );
+ }
+
+ public function loadedDrive($refresh_func, $out_id, $result) {
+ $messages_log = $this->getModule('messages_log');
+ if ($result->error === 0) {
+ $rlen = count($result->output);
+ $last = $rlen > 0 ? trim($result->output[$rlen-1]) : '';
+ if ($last === 'quit') {
+ array_pop($result->output);
+ }
+ $messages_log->append($result->output);
+ } else {
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $messages_log->append($result->output);
+ }
+ if ($result->error === 0) {
+ if (count($result->output) > 0) {
+ // refresh output periodically
+ $this->getCallbackClient()->callClientFunction(
+ $refresh_func,
+ [$out_id]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ // finish refreshing output
+ $this->loadAutochanger(null, null);
+ }
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $this->getModule('messages_log')->append([$emsg]);
+ }
+ }
+
+ public function unloadDrive($sender, $param) {
+ $data = $param->getCallbackParameter();
+ if (!is_object($data)) {
+ return;
+ }
+ $parameters = [
+ 'device' => $data->drive,
+ 'slot' => $data->slot
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->set([
+ 'storages',
+ $this->getStorageId(),
+ 'release',
+ $query
+ ]);
+ if ($result->error === 0) {
+ $out_id = '';
+ if (count($result->output) == 1) {
+ $out = json_decode($result->output[0]);
+ if (is_object($out) && property_exists($out, 'out_id')) {
+ $out_id = $out->out_id;
+ }
+ }
+ $this->getCallbackClient()->callClientFunction(
+ 'oDrives.refresh_drive_unloading',
+ [$out_id]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $this->getModule('messages_log')->append([$emsg]);
+ }
+ }
+
+ public function unloadedDrive($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $parameters = [
+ 'out_id' => $out_id
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->get([
+ 'storages',
+ $this->getStorageId(),
+ 'release',
+ $query
+ ]);
+ $messages_log = $this->getModule('messages_log');
+ if ($result->error === 0) {
+ $rlen = count($result->output);
+ $last = $rlen > 0 ? trim($result->output[$rlen-1]) : '';
+ if ($last === 'quit') {
+ array_pop($result->output);
+ }
+ $messages_log->append($result->output);
+ if (count($result->output) > 0) {
+ // refresh output periodically
+ $this->getCallbackClient()->callClientFunction(
+ 'oDrives.refresh_drive_unloading',
+ [$out_id]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ // finish refreshing output
+ $this->loadAutochanger(null, null);
+ }
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $messages_log->append($result->output);
+ }
+ }
+
+ public function labelBarcodes($sender, $param) {
+ $slots_ach = explode('|', $param->getCallbackParameter());
+ $this->LabelBarcodes->setSlots($slots_ach);
+ $this->LabelBarcodes->loadValues();
+ }
+
+ public function labelComplete($sender, $param) {
+ $this->getCallbackClient()->callClientFunction(
+ 'show_label_volume_window',
+ [false]
+ );
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $this->loadAutochanger(null, null);
+ }
+
+ private function transferSlots($slotsrc, $slotdest) {
+ $parameters = [
+ 'slotsrc' => $slotsrc,
+ 'slotdest' => $slotdest
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->set([
+ 'devices',
+ $this->getDeviceName(),
+ 'transfer',
+ $query
+ ]);
+ return $result;
+ }
+
+ private function getTransferOutput($out_id) {
+ $parameters = [
+ 'out_id' => $out_id
+ ];
+ $query = '?' . http_build_query($parameters);
+ $result = $this->getModule('api')->get([
+ 'devices',
+ $this->getDeviceName(),
+ 'transfer',
+ $query
+ ]);
+ return $result;
+ }
+
+ public function moveToIE($sender, $param) {
+ list($slot_ach, $ie_slot) = explode(',', $param->getCallbackParameter(), 2);
+ $result = $this->transferSlots($slot_ach, $ie_slot);
+ if ($result->error === 0) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.refresh_move_to_ie_loading',
+ [$result->output->out_id]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $this->getModule('messages_log')->append([$emsg]);
+ }
+ }
+
+ public function movingToIE($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $result = $this->getTransferOutput($out_id);
+ $messages_log = $this->getModule('messages_log');
+ if ($result->error === 0) {
+ if (count($result->output) > 0) {
+ // refresh output periodically
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.refresh_move_to_ie_loading',
+ [$out_id]
+ );
+ $rlen = count($result->output);
+ $last = $rlen > 0 ? trim($result->output[$rlen-1]) : '';
+ if ($last === 'quit') {
+ array_pop($result->output);
+ }
+ $messages_log->append($result->output);
+ $out = implode(PHP_EOL, $result->output);
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.set_move_to_ie_log',
+ [$out]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.move_to_ie'
+ );
+ // finish refreshing output
+ $this->loadAutochanger(null, null);
+ }
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $messages_log->append($result->output);
+ }
+ }
+
+ public function releaseIE($sender, $param) {
+ list($ie_slot, $slot_ach) = explode(',', $param->getCallbackParameter(), 2);
+ $result = $this->transferSlots($ie_slot, $slot_ach);
+ if ($result->error === 0) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.refresh_release_ie_loading',
+ [$result->output->out_id]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $this->getModule('messages_log')->append([$emsg]);
+ }
+ }
+
+ public function releasingIE($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $result = $this->getTransferOutput($out_id);
+ $messages_log = $this->getModule('messages_log');
+ if ($result->error === 0) {
+ if (count($result->output) > 0) {
+ // refresh output periodically
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.refresh_release_ie_loading',
+ [$out_id]
+ );
+ $rlen = count($result->output);
+ $last = $rlen > 0 ? trim($result->output[$rlen-1]) : '';
+ if ($last === 'quit') {
+ array_pop($result->output);
+ }
+ $messages_log->append($result->output);
+ $out = implode(PHP_EOL, $result->output);
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.set_release_ie_log',
+ [$out]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.release_ie',
+ [true]
+ );
+
+ // finish refreshing output
+ $this->loadAutochanger(null, null);
+ }
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $messages_log->append($result->output);
+ }
+ }
+
+ public function updateSlotsBarcodes($sender, $param) {
+ $slots_ach = explode('|', $param->getCallbackParameter());
+ $this->UpdateSlots->BarcodeUpdate = true;
+ $this->UpdateSlots->setSlots($slots_ach);
+ $this->UpdateSlots->loadValues();
+ }
+
+ public function updateSlots($sender, $param) {
+ $slots_ach = explode('|', $param->getCallbackParameter());
+ $this->UpdateSlots->BarcodeUpdate = false;
+ $this->UpdateSlots->setSlots($slots_ach);
+ $this->UpdateSlots->loadValues();
+ }
+
+ public function moveFromIE($sender, $param) {
+ list($ie_slot, $slot_ach) = explode(',', $param->getCallbackParameter(), 2);
+ $result = $this->transferSlots($ie_slot, $slot_ach);
+ if ($result->error === 0) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.refresh_release_all_ie_loading',
+ [$result->output->out_id]
+ );
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $this->getModule('messages_log')->append([$emsg]);
+ }
+ }
+
+ public function movingFromIE($sender, $param) {
+ $out_id = $param->getCallbackParameter();
+ $result = $this->getTransferOutput($out_id);
+ $messages_log = $this->getModule('messages_log');
+ if ($result->error === 0) {
+ if (count($result->output) > 0) {
+ // refresh output periodically
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.refresh_release_all_ie_loading',
+ [$out_id]
+ );
+ $rlen = count($result->output);
+ $last = $rlen > 0 ? trim($result->output[$rlen-1]) : '';
+ if ($last === 'quit') {
+ array_pop($result->output);
+ }
+ $messages_log->append($result->output);
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.release_all_ie'
+ );
+ // finish refreshing output
+ $this->loadAutochanger(null, null);
+ }
+ } else {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ $emsg = sprintf('Error %s: %s', $result->error, $result->output);
+ $messages_log->append($result->output);
+ }
+ }
+
+ public function showChangerLoading($sender, $param) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [true]
+ );
+ }
+
+ public function hideChangerLoading($sender, $param) {
+ $this->getCallbackClient()->callClientFunction(
+ 'oSlots.show_changer_loader',
+ [false]
+ );
+ }
}
?>
* 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
*/
class LabelVolume extends Portlets {
+ const SHOW_BUTTON = 'ShowButton';
+ const BARCODE_LABEL = 'BarcodeLabel';
+ const STORAGE = 'Storage';
+
public function loadValues() {
$storages = $this->getModule('api')->get(array('storages'));
$storage_list = array();
$storage_list[$storage->storageid] = $storage->name;
}
}
- $this->StorageLabel->dataSource = $storage_list;
+ $this->StorageLabel->DataSource = $storage_list;
+ if ($this->Storage) {
+ $storage_list_flip =array_flip($storage_list);
+ $this->StorageLabel->SelectedValue = $storage_list_flip[$this->Storage];
+ }
$this->StorageLabel->dataBind();
$pools = $this->Application->getModule('api')->get(array('pools'));
public function labelVolumes($sender, $param) {
$result = null;
- if ($this->Barcodes->Checked == true) {
+ if ($this->Barcodes->Checked || $this->BarcodeLabel) {
$params = array(
'slots' => $this->SlotsLabel->Text,
'drive' => $this->DriveLabel->Text,
if ($result->error === 0) {
$this->getPage()->getCallbackClient()->callClientFunction('set_labeling_status', array('loading'));
$this->LabelVolumeLog->Text = implode('', $result->output);
+ $this->onLabelStart($param);
} else {
$this->LabelVolumeLog->Text = $result->output;
+ $this->onLabelFail($param);
}
}
$this->getPage()->getCallbackClient()->callClientFunction('label_volume_output_refresh', array($out_id));
} else {
$this->getPage()->getCallbackClient()->callClientFunction('set_labeling_status', array('finish'));
+ $this->onLabelSuccess($param);
+ $this->onLabelComplete($param);
}
} else {
$this->LabelVolumeLog->Text = $result->output;
+ $this->onLabelFail($param);
+ $this->onLabelComplete($param);
}
}
+
+ public function onLabelStart($param) {
+ $this->raiseEvent('OnLabelStart', $this, $param);
+ }
+
+ public function onLabelComplete($param) {
+ $this->raiseEvent('OnLabelComplete', $this, $param);
+ }
+
+ public function onLabelSuccess($param) {
+ $this->raiseEvent('OnLabelSuccess', $this, $param);
+ }
+
+ public function onLabelFail($param) {
+ $this->raiseEvent('OnLabelFail', $this, $param);
+ }
+
+ public function setSlots(array $slots) {
+ $this->SlotsLabel->Text = implode(',', $slots);
+ }
+
+ public function setShowButton($show) {
+ $show = TPropertyValue::ensureBoolean($show);
+ $this->setViewState(self::SHOW_BUTTON, $show);
+ }
+
+ public function getShowButton() {
+ return $this->getViewState(self::SHOW_BUTTON, true);
+ }
+
+ public function setBarcodeLabel($barcode_label) {
+ $barcode_label = TPropertyValue::ensureBoolean($barcode_label);
+ $this->setViewState(self::BARCODE_LABEL, $barcode_label);
+ }
+
+ public function getBarcodeLabel() {
+ return $this->getViewState(self::BARCODE_LABEL);
+ }
+
+ public function setStorage($storage) {
+ $this->setViewState(self::STORAGE, $storage);
+ }
+
+ public function getStorage() {
+ return $this->getViewState(self::STORAGE);
+ }
}
?>
<com:TActiveLinkButton
CssClass="w3-button w3-green"
OnClick="loadValues"
+ Visible="<%=$this->ShowButton%>"
>
<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';
+ show_label_volume_window(true);
</prop:Attributes.onclick>
<i class="fa fa-tag"></i> <%[ Label volume(s) ]%>
</com:TActiveLinkButton>
<div id="label_volume" class="w3-modal">
<div class="w3-modal-content w3-animate-top w3-card-4">
<header class="w3-container w3-teal">
- <span onclick="document.getElementById('label_volume').style.display='none'" class="w3-button w3-display-topright">×</span>
+ <span onclick="show_label_volume_window(false);" class="w3-button w3-display-topright">×</span>
<h2><%[ Label volume(s) ]%></h2>
</header>
<div class="w3-padding">
/>
<div class="w3-row-padding w3-section-padding w3-section">
<div class="w3-col w3-half"><com:TLabel ForControl="Barcodes" Text="<%[ Use barcodes as label: ]%>" /></div>
- <div class="w3-col w3-half"><com:TActiveCheckBox ID="Barcodes" CssClass="w3-check" Attributes.onclick="set_barcodes();"/></div>
+ <div class="w3-col w3-half"><com:TActiveCheckBox ID="Barcodes" CssClass="w3-check" Attributes.onclick="set_label_volume_barcodes();"/></div>
</div>
<div id="label_with_name" class="w3-row-padding w3-section">
<div class="w3-col w3-half"><com:TLabel ForControl="LabelName" Text="<%[ Label name: ]%>" /></div>
<div class="w3-col w3-half"><com:TLabel ForControl="PoolLabel" Text="<%[ Pool: ]%>" /></div>
<div class="w3-col w3-half"><com:TActiveDropDownList ID="PoolLabel" CssClass="w3-select w3-border" /></div>
</div>
- <div class="w3-row-padding w3-section">
+ <div class="w3-row-padding w3-section"<%=$this->Storage ? ' style="display: none"' : ''%>>
<div class="w3-col w3-half"><com:TLabel ForControl="StorageLabel" Text="<%[ Storage: ]%>" /></div>
<div class="w3-col w3-half"><com:TActiveDropDownList ID="StorageLabel" CssClass="w3-select w3-border" /></div>
</div>
<div class="w3-row-padding w3-section">
- <div class="w3-col w3-half"><com:TLabel ForControl="DriveLabel" Text="<%[ Drive number: ]%>" /></div>
+ <div class="w3-col w3-half"><com:TLabel ForControl="DriveLabel" Text="<%[ Drive index: ]%>" /></div>
<div class="w3-col w3-half">
<com:TActiveTextBox ID="DriveLabel" CssClass="w3-input w3-border" Text="0" />
<com:TRequiredFieldValidator
</div>
</div>
<div class="w3-container w3-center w3-section">
- <button type="button" class="w3-button w3-red" onclick="document.getElementById('label_volume').style.display='none';"><i class="fa fa-times"></i> <%[ Close ]%></button>
+ <button type="button" class="w3-button w3-red" onclick="show_label_volume_window(false);"><i class="fa fa-times"></i> <%[ Close ]%></button>
<com:TActiveLinkButton
ID="LabelButton"
CausesValidation="true"
</com:TCallback>
<script type="text/javascript">
var label_volume_logbox_scroll = false;
-function set_barcodes() {
+function set_label_volume_barcodes(force_barcodes) {
var chkb = document.getElementById('<%=$this->Barcodes->ClientID%>');
+ if (force_barcodes) {
+ chkb.checked = true;
+ chkb.setAttribute('disabled', 'disabled');
+ }
var name_el = document.getElementById('label_with_name');
var with_barcodes_el = document.getElementById('label_with_barcodes');
var without_barcodes_el = document.getElementById('label_without_barcodes');
cb.setCallbackParameter(out_id);
cb.dispatch();
}
+
+function show_label_volume_window(show) {
+ 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 = show ? 'block' : 'none';
+}
+
+<%=$this->getBarcodeLabel() ? 'set_label_volume_barcodes(true);' : ''%>
</script>
* 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
*/
class UpdateSlots extends Portlets {
+ const SHOW_BUTTON = 'ShowButton';
+ const BARCODE_UPDATE = 'BarcodeUpdate';
+ const STORAGE = 'Storage';
+
public function loadValues() {
$storages = $this->getModule('api')->get(array('storages'));
$storage_list = array();
$storage_list[$storage->storageid] = $storage->name;
}
}
- $this->StorageUpdate->dataSource = $storage_list;
+ $this->StorageUpdate->DataSource = $storage_list;
+ if ($this->Storage) {
+ $storage_list_flip =array_flip($storage_list);
+ $this->StorageUpdate->SelectedValue = $storage_list_flip[$this->Storage];
+ }
$this->StorageUpdate->dataBind();
}
public function update($sender, $param) {
$url_params = array();
- if($this->Barcodes->Checked == true) {
+ if($this->Barcodes->Checked == true || $this->BarcodeUpdate) {
$url_params = array('volumes', 'update', 'barcodes');
} else {
$url_params = array('volumes', 'update');
$this->getPage()->getCallbackClient()->callClientFunction('update_slots_output_refresh', array($out->out_id));
}
}
+
if ($result->error === 0) {
$this->getPage()->getCallbackClient()->callClientFunction('set_updating_status', array('loading'));
$this->UpdateSlotsLog->Text = implode('', $result->output);
+ $this->onUpdateStart($param);
} else {
$this->UpdateSlotsLog->Text = $result->output;
+ $this->onUpdateFail($param);
}
}
$this->getPage()->getCallbackClient()->callClientFunction('update_slots_output_refresh', array($out_id));
} else {
$this->getPage()->getCallbackClient()->callClientFunction('set_updating_status', array('finish'));
+ $this->onUpdateSuccess($param);
+ $this->onUpdateComplete($param);
}
} else {
$this->UpdateSlotsLog->Text = $result->output;
+ $this->onUpdateFail($param);
+ $this->onUpdateComplete($param);
}
}
+ public function onUpdateStart($param) {
+ $this->raiseEvent('OnUpdateStart', $this, $param);
+ }
+
+ public function onUpdateComplete($param) {
+ $this->raiseEvent('OnUpdateComplete', $this, $param);
+ }
+
+ public function onUpdateSuccess($param) {
+ $this->raiseEvent('OnUpdateSuccess', $this, $param);
+ }
+
+ public function onUpdateFail($param) {
+ $this->raiseEvent('OnUpdateFail', $this, $param);
+ }
+
+ public function setSlots(array $slots) {
+ $this->SlotsUpdate->Text = implode(',', $slots);
+ }
+
+ public function setShowButton($show) {
+ $show = TPropertyValue::ensureBoolean($show);
+ $this->setViewState(self::SHOW_BUTTON, $show);
+ }
+
+ public function getShowButton() {
+ return $this->getViewState(self::SHOW_BUTTON, true);
+ }
+
+ public function setBarcodeUpdate($barcode_update) {
+ $barcode_update = TPropertyValue::ensureBoolean($barcode_update);
+ $this->setViewState(self::BARCODE_UPDATE, $barcode_update);
+ }
+
+ public function getBarcodeUpdate() {
+ return $this->getViewState(self::BARCODE_UPDATE);
+ }
+
+ public function setStorage($storage) {
+ $this->setViewState(self::STORAGE, $storage);
+ }
+
+ public function getStorage() {
+ return $this->getViewState(self::STORAGE);
+ }
}
?>
<com:TActiveLinkButton
CssClass="w3-button w3-green"
OnClick="loadValues"
+ Visible="<%=$this->ShowButton%>"
>
<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> <%[ Update slots ]%>
</com:TActiveLinkButton>
/>
<div class="w3-row-padding w3-section-padding w3-section">
<div class="w3-col w3-half"><com:TLabel ForControl="Barcodes" Text="<%[ Update slots using barcodes ]%>" /></div>
- <div class="w3-col w3-half"><com:TActiveCheckBox ID="Barcodes" CssClass="w3-check" Checked="true" /></div>
+ <div class="w3-col w3-half"><com:TActiveCheckBox ID="Barcodes" CssClass="w3-check" Checked="true" Attributes.onclick="set_update_slots_barcodes();" /></div>
</div>
- <div class="w3-row-padding w3-section">
+ <div class="w3-row-padding w3-section"<%=$this->Storage ? ' style="display: none"' : ''%>>
<div class="w3-col w3-half"><com:TLabel ForControl="StorageUpdate" Text="<%[ Storage: ]%>" /></div>
<div class="w3-col w3-half"><com:TActiveDropDownList ID="StorageUpdate" CssClass="w3-select w3-border" /></div>
</div>
</com:TCallback>
<script type="text/javascript">
var update_slots_logbox_scroll = false;
+function set_update_slots_barcodes(force_barcodes) {
+ var chkb = document.getElementById('<%=$this->Barcodes->ClientID%>');
+ if (force_barcodes) {
+ chkb.checked = true;
+ chkb.setAttribute('disabled', 'disabled');
+ }
+}
+function set_update_slots(force) {
+ var chkb = document.getElementById('<%=$this->Barcodes->ClientID%>');
+ if (force) {
+ chkb.checked = false;
+ chkb.setAttribute('disabled', 'disabled');
+ }
+}
function set_updating_status(status) {
var start = document.getElementById('update_slots_status_start');
var loading = document.getElementById('update_slots_status_loading');
cb.setCallbackParameter(out_id);
cb.dispatch();
}
+function show_update_slots_window() {
+ 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';
+}
+<%=$this->getBarcodeUpdate() ? 'set_update_slots_barcodes(true);' : ''%>
</script>