]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Implement support for assigning multiple API hosts to one user
authorMarcin Haba <marcin.haba@bacula.pl>
Sat, 17 Apr 2021 16:14:00 +0000 (18:14 +0200)
committerMarcin Haba <marcin.haba@bacula.pl>
Sat, 17 Apr 2021 16:14:00 +0000 (18:14 +0200)
Changes:
 - managing many API hosts assigned to user without need to re-login for each API host
 - support for autochanger management on remote API hosts
 - new tabs to configure remote file daemon and storage daemons directly on Storage page or Client page

41 files changed:
gui/baculum/protected/Common/Class/BClientScript.php
gui/baculum/protected/Common/Class/ConfigIni.php
gui/baculum/protected/Common/Class/OAuth2.php
gui/baculum/protected/Common/Portlets/BSimpleRepeater.php [new file with mode: 0644]
gui/baculum/protected/Common/Portlets/BSimpleRepeaterItem.php [new file with mode: 0644]
gui/baculum/protected/Web/Class/BaculumAPIClient.php
gui/baculum/protected/Web/Class/BaculumWebPage.php
gui/baculum/protected/Web/Class/PageCategory.php
gui/baculum/protected/Web/Class/WebUser.php
gui/baculum/protected/Web/Class/WebUserConfig.php
gui/baculum/protected/Web/Data/data_desc.json
gui/baculum/protected/Web/JavaScript/misc.js
gui/baculum/protected/Web/Lang/en/messages.mo
gui/baculum/protected/Web/Lang/en/messages.po
gui/baculum/protected/Web/Lang/ja/messages.mo
gui/baculum/protected/Web/Lang/ja/messages.po
gui/baculum/protected/Web/Lang/pl/messages.mo
gui/baculum/protected/Web/Lang/pl/messages.po
gui/baculum/protected/Web/Lang/pt/messages.mo
gui/baculum/protected/Web/Lang/pt/messages.po
gui/baculum/protected/Web/Lang/ru/messages.mo
gui/baculum/protected/Web/Lang/ru/messages.po
gui/baculum/protected/Web/Layouts/Main.php
gui/baculum/protected/Web/Layouts/Main.tpl
gui/baculum/protected/Web/Pages/ClientView.page
gui/baculum/protected/Web/Pages/ClientView.php
gui/baculum/protected/Web/Pages/NewResource.php
gui/baculum/protected/Web/Pages/ScheduleList.page
gui/baculum/protected/Web/Pages/Security.page
gui/baculum/protected/Web/Pages/Security.php
gui/baculum/protected/Web/Pages/SelectAPIHost.page [new file with mode: 0644]
gui/baculum/protected/Web/Pages/SelectAPIHost.php [new file with mode: 0644]
gui/baculum/protected/Web/Pages/StorageView.page
gui/baculum/protected/Web/Pages/StorageView.php
gui/baculum/protected/Web/Portlets/BaculaConfigResourceList.php [new file with mode: 0644]
gui/baculum/protected/Web/Portlets/BaculaConfigResourceList.tpl [new file with mode: 0644]
gui/baculum/protected/Web/Portlets/BaculaConfigResources.php
gui/baculum/protected/Web/Portlets/DirectiveControlTemplate.php
gui/baculum/protected/Web/Portlets/DirectiveMessages.php
gui/baculum/protected/Web/Portlets/DirectiveMessages.tpl
gui/baculum/protected/Web/endpoints.xml

index ce92ed0d599a80f8c2de8cb9a615c1d3110c1191..7f41cf579123dc742a41493b22ff49b922b90873 100644 (file)
@@ -31,7 +31,7 @@ Prado::using('System.Web.UI.WebControls.TClientScript');
  */
 class BClientScript extends TClientScript {
 
-       const SCRIPTS_VERSION = 18;
+       const SCRIPTS_VERSION = 19;
 
        public function getScriptUrl()
        {
index 5ac57275e355f03e5fe42fbdd8ccf91cd24b6521..d73947e524b9cc4b6bb6fd2905abea1a70d51a18 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2021 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -77,8 +77,14 @@ class ConfigIni extends CommonModule implements ConfigFormat {
                        $content .= "[$section]\n";
                                foreach($options as $option => $value) {
                                        if (is_array($value)) {
+                                               $str_keys = array_filter(array_keys($value), 'is_string');
+                                               $is_assoc = (count($str_keys) > 0); // check if array is associative
                                                foreach($value as $k => $v) {
                                                        $v = $this->prepareValue($v);
+                                                       if (!$is_assoc) {
+                                                               // array with numeric indexes, set empty key
+                                                               $k = '';
+                                                       }
                                                        $content .= "{$option}[$k] = $v\n";
                                                }
                                        } else {
index 485b39403bb437463eab87656df51a4b336eefc2..0b55fe45edcb6adbc8a25c06aeede4f8d05074b2 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2021 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -87,7 +87,7 @@ abstract class OAuth2 extends CommonModule {
         * Temportary set to 15 minutes for testst purposes.
         * In production the value SHOULD BE changed.
         */
-       const ACCESS_TOKEN_EXPIRES_TIME = 90000;
+       const ACCESS_TOKEN_EXPIRES_TIME = 120;
 
        /**
         * Scope pattern.
diff --git a/gui/baculum/protected/Common/Portlets/BSimpleRepeater.php b/gui/baculum/protected/Common/Portlets/BSimpleRepeater.php
new file mode 100644 (file)
index 0000000..bd8e40e
--- /dev/null
@@ -0,0 +1,93 @@
+<?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.WebControls.TDataBoundControl');
+Prado::using('Application.Common.Portlets.BSimpleRepeaterItem');
+
+/**
+ * Baculum simple repeater control.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Control
+ * @package Baculum Web
+ */
+class BSimpleRepeater extends TDataBoundControl {
+
+       /**
+        * Stores item template object
+        */
+       private $_itemTemplate;
+
+       /**
+        * Get template for items.
+        *
+        * @return ITemplate template
+        */
+       public function getItemTemplate() {
+               return $this->_itemTemplate;
+       }
+
+       /**
+        * Set template for items.
+        *
+        * @param ITemplate $tpl template
+        * @return none
+        */
+       public function setItemTemplate($tpl) {
+               if ($tpl instanceof \Prado\Web\UI\ITemplate) {
+                       $this->_itemTemplate = $tpl;
+               }
+       }
+
+       /**
+        * Data binding.
+        *
+        * @param $data data from data source
+        * @return none
+        */
+       protected function performDataBinding($data) {
+               for ($i = 0; $i < count($data); $i++) {
+                       $this->createItem($data[$i]);
+               }
+       }
+
+       /**
+        * Create single repeater item.
+        *
+        * return BSimpleRepeaterItem repeater item
+        */
+       private function createItem($data) {
+               $item = new BSimpleRepeaterItem;
+               if ($item instanceof \Prado\IDataRenderer) {
+                       $item->setData($data);
+               }
+               if ($this->_itemTemplate) {
+                       $this->_itemTemplate->instantiateIn($item, $this);
+               }
+               return $item;
+       }
+
+       public function render($writer) {
+               $this->renderChildren($writer);
+       }
+}
+?>
diff --git a/gui/baculum/protected/Common/Portlets/BSimpleRepeaterItem.php b/gui/baculum/protected/Common/Portlets/BSimpleRepeaterItem.php
new file mode 100644 (file)
index 0000000..9a72997
--- /dev/null
@@ -0,0 +1,44 @@
+<?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.TControl');
+
+/**
+ * Baculum simple repeater item control.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Control
+ * @package Baculum Web
+ */
+class BSimpleRepeaterItem extends \Prado\Web\UI\TControl implements \Prado\IDataRenderer {
+
+       private $_data = [];
+
+       public function getData() {
+               return $this->_data;
+       }
+
+       public function setData($data) {
+               $this->_data = $data;
+       }
+}
+?>
index 9f139367a672622ce1682131dc5f7a4c40303570..c4602f35754948a46f653d08bc30b83860b02596 100644 (file)
@@ -292,7 +292,7 @@ class BaculumAPIClient extends WebModule {
                $cached = null;
                $ret = null;
                if (is_null($host)) {
-                       $host = $this->User->getAPIHosts();
+                       $host = $this->User->getDefaultAPIHost();
                }
                if ($use_cache === true) {
                        $cached = $this->getSessionCache($host, $params);
@@ -333,7 +333,7 @@ class BaculumAPIClient extends WebModule {
         */
        public function set(array $params, array $options = array(), $host = null, $show_error = true) {
                if (is_null($host)) {
-                       $host = $this->User->getAPIHosts();
+                       $host = $this->User->getDefaultAPIHost();
                }
                $host_cfg = $this->getHostParams($host);
                $uri = $this->getURIResource($host, $params);
@@ -370,7 +370,7 @@ class BaculumAPIClient extends WebModule {
         */
        public function create(array $params, array $options, $host = null, $show_error = true) {
                if (is_null($host)) {
-                       $host = $this->User->getAPIHosts();
+                       $host = $this->User->getDefaultAPIHost();
                }
                $host_cfg = $this->getHostParams($host);
                $uri = $this->getURIResource($host, $params);
@@ -402,7 +402,7 @@ class BaculumAPIClient extends WebModule {
         */
        public function remove(array $params, $host = null, $show_error = true) {
                if (is_null($host)) {
-                       $host = $this->User->getAPIHosts();
+                       $host = $this->User->getDefaultAPIHost();
                }
                $host_cfg = $this->getHostParams($host);
                $uri = $this->getURIResource($host, $params);
index 703d7762554d7ccf6f027fc8522e4b6453b9ed3b..2e6a4c459fdb1dc4216ed696c847ee7496a3d299 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2020 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
@@ -53,6 +53,13 @@ class BaculumWebPage extends BaculumPage {
                        // without config there is no way to call api below
                        return;
                }
+
+               if (!$this->IsCallBack && !$this->IsPostBack && !$this->isDefaultAPIHost()) {
+                       $this->goToPage('SelectAPIHost');
+                       // without API host selected we can't continue
+                       return;
+               }
+
                Logging::$debug_enabled = (isset($this->web_config['baculum']['debug']) && $this->web_config['baculum']['debug'] == 1);
                if (!$this->IsPostBack && !$this->IsCallBack) {
                        $this->postInitActions();
@@ -61,6 +68,20 @@ class BaculumWebPage extends BaculumPage {
                }
        }
 
+       /**
+        * Check if default API host is set.
+        * If it isn't direct to API host selection page.
+        *
+        * @return none
+        */
+       private function isDefaultAPIHost() {
+               $def_api_host = $this->User->getDefaultAPIHost();
+               $auth = $this->getModule('auth');
+               $page = $this->Service->getRequestedPagePath();
+               $pages_no_host = [$auth->getLoginPage(), 'SelectAPIHost'];
+               return (!is_null($def_api_host) || in_array($page, $pages_no_host));
+       }
+
        /**
         * Set page session values.
         *
index 6f3976f2a75f7f17f37aba5943011f7e68ac697e..f15c0949508d62d3eb24f0a72230a92daad079b6 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2020 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
@@ -50,6 +50,7 @@ class PageCategory extends WebModule {
         */
        const MONITOR = 'Monitor';
        const BACULUM_ERROR = 'BaculumError';
+       const SELECT_API_HOST = 'SelectAPIHost';
 
        /**
         * Public pages - always allowed
@@ -87,7 +88,8 @@ class PageCategory extends WebModule {
        private function getSystemCategories() {
                return [
                        self::MONITOR,
-                       self::BACULUM_ERROR
+                       self::BACULUM_ERROR,
+                       self::SELECT_API_HOST
                ];
        }
 
index 3b9e22bcd457aa882a59edcac377ff2e378e5eea..3d2652327fdc13e597d39131185be53c041592cc 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2020 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
@@ -37,6 +37,7 @@ class WebUser extends TUser {
        const EMAIL = 'Email';
        const DESCRIPTION = 'Description';
        const API_HOSTS = 'ApiHosts';
+       const DEFAULT_API_HOST = 'DefaultApiHost';
        const IPS = 'Ips';
        const ENABLED = 'Enabled';
        const IN_CONFIG = 'InConfig';
@@ -162,10 +163,8 @@ class WebUser extends TUser {
 
        /**
         * Set API hosts.
-        * So far is supported only one API host per user.
-        * In the future this method can support more API hosts per user.
         *
-        * @param string $api_hosts API hosts
+        * @param array $api_hosts user API hosts
         * @return none
         */
        public function setAPIHosts($api_hosts) {
@@ -175,20 +174,80 @@ class WebUser extends TUser {
        /**
         * API hosts getter.
         *
-        * @return string API host
+        * @return array user API hosts
         */
        public function getAPIHosts() {
+               $api_hosts = [];
                $hosts = $this->getState(self::API_HOSTS);
-               $hosts = explode(',', $hosts);
-               if (count($hosts) == 1 && !empty($hosts[0])) {
-                       $api_hosts = $hosts[0];
+               /**
+                * This checking is for backward compatibility because previously
+                * hosts were written in session as string. Now it is written as array.
+                */
+               if (is_string($hosts)) {
+                       if (!empty($hosts)) {
+                               $hosts = explode(',', $hosts);
+                       } else {
+                               $hosts = [];
+                       }
+               } elseif (is_null($hosts)) {
+                       $hosts = [];
+               }
+
+               if (count($hosts) > 0) {
+                       $api_hosts = $hosts;
                } else {
-                       // default API host
-                       $api_hosts = HostConfig::MAIN_CATALOG_HOST;
+                       // add default API host
+                       $api_hosts[] = HostConfig::MAIN_CATALOG_HOST;
                }
                return $api_hosts;
        }
 
+       /**
+        * Set default API host for user.
+        * It determines which host will be used as default API host to login
+        * to Baculum Web interface. This host needs to have at least the catalog
+        * and the console capabilities.
+        *
+        * @param string $api_host default API host
+        * @return none
+        */
+       public function setDefaultAPIHost($api_host) {
+               $this->setState(self::DEFAULT_API_HOST, $api_host);
+               $application = $this->getManager()->getApplication();
+               $application->getModule('auth')->updateSessionUser($this);
+       }
+
+       /**
+        * Get default API host for user.
+        * If default API host is not set, there happens a try to determine
+        * this host if user has only one API host assigned.
+        *
+        * @return string|null default API host or null if no default host set
+        */
+       public function getDefaultAPIHost() {
+               $def_host = $this->getState(self::DEFAULT_API_HOST);
+               if (!$def_host) {
+                       $api_hosts = $this->getAPIHosts();
+                       if (count($api_hosts) == 1) {
+                               // only one host assigned, so use it as default host
+                               $def_host = $api_hosts[0];
+                               $this->setDefaultAPIHost($def_host);
+                       }
+               }
+               return $def_host;
+       }
+
+       /**
+        * Check if given API host belongs to user API hosts.
+        *
+        * @param string $api_host API host to check
+        * @return boolean true if API host belongs to user API hosts, otherwise false
+        */
+       public function isUserAPIHost($api_host) {
+               $api_hosts = $this->getAPIHosts();
+               return in_array($api_host, $api_hosts);
+       }
+
        /**
         * IP address restriction setter.
         *
index 256a8b04d72bc8be2863f806ffcf3f4408083dae..d7eb33adee579e0a77ef7981525aa06c342636ce 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2020 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
@@ -70,7 +70,6 @@ class WebUserConfig extends ConfigFileModule {
                'description',
                'email',
                'roles',
-               'api_hosts',
                'enabled',
                'ips'
        ];
@@ -97,6 +96,14 @@ class WebUserConfig extends ConfigFileModule {
                                continue;
                        }
                        $user_config['username'] = $username;
+                       // for API hosts backward compatibility
+                       if (key_exists('api_hosts', $user_config)) {
+                               if (!is_array($user_config['api_hosts'])) {
+                                       $user_config['api_hosts'] = !empty($user_config['api_hosts']) ? [$user_config['api_hosts']] : [];
+                               }
+                       } else {
+                               $user_config['api_hosts'] = [];
+                       }
                        $config[$username] = $user_config;
                }
                return $config;
index 027fb844b95d639f95735bcb935989174da6c616..25af95d416d36f59b9f4955721a344fc1eacbb5e 100644 (file)
                },
                "Messages": {
                        "Name": {
-                               "Required": false,
+                               "Required": true,
                                "ValueType": "name",
                                "DefaultValue": 0,
                                "FieldType": "TextBox",
                },
                "Messages": {
                        "Name": {
-                               "Required": false,
+                               "Required": true,
                                "ValueType": "name",
                                "DefaultValue": 0,
                                "FieldType": "TextBox",
                },
                "Messages": {
                        "Name": {
-                               "Required": false,
+                               "Required": true,
                                "ValueType": "name",
                                "DefaultValue": 0,
                                "FieldType": "TextBox",
index 907b866d88adf0d9713089d3214383b866fb77d6..f056478aec10306b9b46ca8ca961af035f17a84d 100644 (file)
@@ -989,9 +989,9 @@ function openElementOnCursor(e, element, offsetX, offsetY) {
                offsetY = 0;
        }
        var x = (e.clientX + offsetX).toString();
-       var y = (e.clientY + offsetY + window.pageYOffset).toString();
+       var y = (e.clientY + offsetY).toString();
        $('#' + element).css({
-               position: 'absolute',
+               position: 'fixed',
                left: x + 'px',
                top: y + 'px',
                zIndex: 1000
index 44a01c253e1cd454c8e4d441bb44d39d33b41926..44fddc761a45c0c6d48f688940a54dda3f7cbe97 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ
index 71883eadbe54d3fca9b4efcb864d56dd2bfca7fd..58ab65d90be9d55c2c39ba9bd53436af22190c3b 100644 (file)
@@ -2801,8 +2801,8 @@ msgstr "Default role for imported users:"
 msgid "Advanced options"
 msgstr "Advanced options"
 
-msgid "Default API host for imported users:"
-msgstr "Default API host for imported users:"
+msgid "Default API hosts for imported users:"
+msgstr "Default API hosts for imported users:"
 
 msgid "Log in"
 msgstr "Log in"
@@ -3301,3 +3301,36 @@ msgstr "Tip: To use bulk autochanger actions, please select table rows."
 
 msgid "Mount volume"
 msgstr "Mount volume"
+
+msgid "API hosts:"
+msgstr "API hosts:"
+
+msgid "Please select API host"
+msgstr "Please select API host"
+
+msgid "FD address"
+msgstr "FD address"
+
+msgid "FD port"
+msgstr "FD port"
+
+msgid "FD API host"
+msgstr "FD API host"
+
+msgid "Configure file daemon"
+msgstr "Configure file daemon"
+
+msgid "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+msgstr "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+
+msgid "SD API host"
+msgstr "SD API host"
+
+msgid "SD address"
+msgstr "SD address"
+
+msgid "SD port"
+msgstr "SD port"
+
+msgid "Configure storage daemon"
+msgstr "Configure storage daemon"
index a039df50c49c63b13a2d00e232b21948790f3d3f..54f869868a51f48c98ed9e1e46fcb5d8568d49e2 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ
index 3893d5868f986c0d93672b237552b4bb066902a4..e1705ce14d8982096cb42dd1fdf957f650c9ca3e 100644 (file)
@@ -2887,8 +2887,8 @@ msgstr "Default role for imported users:"
 msgid "Advanced options"
 msgstr "Advanced options"
 
-msgid "Default API host for imported users:"
-msgstr "Default API host for imported users:"
+msgid "Default API hosts for imported users:"
+msgstr "Default API hosts for imported users:"
 
 msgid "Log in"
 msgstr "Log in"
@@ -3387,3 +3387,36 @@ msgstr "Tip: To use bulk autochanger actions, please select table rows."
 
 msgid "Mount volume"
 msgstr "Mount volume"
+
+msgid "API hosts:"
+msgstr "API hosts:"
+
+msgid "Please select API host"
+msgstr "Please select API host"
+
+msgid "FD address"
+msgstr "FD address"
+
+msgid "FD port"
+msgstr "FD port"
+
+msgid "FD API host"
+msgstr "FD API host"
+
+msgid "Configure file daemon"
+msgstr "Configure file daemon"
+
+msgid "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+msgstr "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+
+msgid "SD API host"
+msgstr "SD API host"
+
+msgid "SD address"
+msgstr "SD address"
+
+msgid "SD port"
+msgstr "SD port"
+
+msgid "Configure storage daemon"
+msgstr "Configure storage daemon"
index f3574c5cb90d8b0eea00fd2392c256ef457878b8..9047ea14f6475a284f6f23696e9f0a109afe4e5d 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ
index e7a797070f30751212606d80657ebb1617ac76b6..1f64bcb374e08f50e0e2466f99027206fb9b3a27 100644 (file)
@@ -2811,8 +2811,8 @@ msgstr "Domyślna rola dla zaimportowanych użytkowników:"
 msgid "Advanced options"
 msgstr "Opcje zaawansowane"
 
-msgid "Default API host for imported users:"
-msgstr "Domyślny host API dla zaimportowanych użytkowników:"
+msgid "Default API hosts for imported users:"
+msgstr "Domyślne hosty API dla zaimportowanych użytkowników:"
 
 msgid "Log in"
 msgstr "Zaloguj"
@@ -3312,3 +3312,35 @@ msgstr "Wskazówka: W celu użycia akcji zbiorczych zmieniarki taśm, proszę za
 msgid "Mount volume"
 msgstr "Montuj wolumen"
 
+msgid "API hosts:"
+msgstr "Hosty API:"
+
+msgid "Please select API host"
+msgstr "Proszę wybrać host API"
+
+msgid "FD address"
+msgstr "Adres FD"
+
+msgid "FD port"
+msgstr "Port FD"
+
+msgid "FD API host"
+msgstr "Host API FD"
+
+msgid "Configure file daemon"
+msgstr "Konfiguruj file daemon"
+
+msgid "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+msgstr "Wystąpił problem z załadowaniem konfiguracji zasobu. Proszę sprawdzić czy wybrany host API działa i czy dostarcza dostęp do konfiguracji tego zasobu."
+
+msgid "SD API host"
+msgstr "Host API SD"
+
+msgid "SD address"
+msgstr "Adres SD"
+
+msgid "SD port"
+msgstr "Port SD"
+
+msgid "Configure storage daemon"
+msgstr "Konfiguruj storage daemon"
index 02617218492b855fb428dba9766f5d30ff7f11ca..7952f0abffcf3d5258a70a39f569c2a56409ab0f 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ
index 8a7d024b1a52b5c51b63f4d1555c8cb2c077a2c7..68c2bfc36dc6e7c578fcd553c45fd53752ae4f75 100644 (file)
@@ -2811,8 +2811,8 @@ msgstr "Função padrão para usuários importados:"
 msgid "Advanced options"
 msgstr "Opções avançadas"
 
-msgid "Default API host for imported users:"
-msgstr "Host API padrão para usuários importados:"
+msgid "Default API hosts for imported users:"
+msgstr "Default API hosts for imported users:"
 
 msgid "Log in"
 msgstr "Efetuar Login"
@@ -3312,3 +3312,35 @@ msgstr "Dica: Para usar ações do autochanger em massa, selecione linhas de tab
 msgid "Mount volume"
 msgstr "Montar volume"
 
+msgid "API hosts:"
+msgstr "API hosts:"
+
+msgid "Please select API host"
+msgstr "Please select API host"
+
+msgid "FD address"
+msgstr "FD address"
+
+msgid "FD port"
+msgstr "FD port"
+
+msgid "FD API host"
+msgstr "FD API host"
+
+msgid "Configure file daemon"
+msgstr "Configure file daemon"
+
+msgid "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+msgstr "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+
+msgid "SD API host"
+msgstr "SD API host"
+
+msgid "SD address"
+msgstr "SD address"
+
+msgid "SD port"
+msgstr "SD port"
+
+msgid "Configure storage daemon"
+msgstr "Configure storage daemon"
index 0401ccbf5f8fde2199ba9123d39600f9e1037791..c96707febc7451faf1b76e93ab4bb44bf48d6abf 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/ru/messages.mo and b/gui/baculum/protected/Web/Lang/ru/messages.mo differ
index 4cfd21a8115dafc2e1f52789add5a55960c024c5..d6f6191bcc55018a9f4d38b76579163ea8d77ddb 100644 (file)
@@ -2811,8 +2811,8 @@ msgstr "Роль по умолчанию для импортированных 
 msgid "Advanced options"
 msgstr "Расширенные настройки"
 
-msgid "Default API host for imported users:"
-msgstr "Хост API по умолчанию для импортированных пользователей:"
+msgid "Default API hosts for imported users:"
+msgstr "Default API hosts for imported users:"
 
 msgid "Log in"
 msgstr "Вход"
@@ -3312,3 +3312,35 @@ msgstr "Подсказка: Чтобы использовать массовые
 msgid "Mount volume"
 msgstr "Монтировать том"
 
+msgid "API hosts:"
+msgstr "API hosts:"
+
+msgid "Please select API host"
+msgstr "Please select API host"
+
+msgid "FD address"
+msgstr "FD address"
+
+msgid "FD port"
+msgstr "FD port"
+
+msgid "FD API host"
+msgstr "FD API host"
+
+msgid "Configure file daemon"
+msgstr "Configure file daemon"
+
+msgid "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+msgstr "There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration."
+
+msgid "SD API host"
+msgstr "SD API host"
+
+msgid "SD address"
+msgstr "SD address"
+
+msgid "SD port"
+msgstr "SD port"
+
+msgid "Configure storage daemon"
+msgstr "Configure storage daemon"
index 217b677a8f6cba1aeaee3e813d8997a905b360d6..5e0e254cbe17419b12c7e99a04a8112c51d8913b 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2021 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -34,9 +34,32 @@ class Main extends TTemplateControl {
 
        public $web_config;
 
+       public function onInit($param) {
+               parent::onInit($param);
+               if ($this->getPage()->IsPostBack || $this->getPage()->IsCallBack) {
+                       return;
+               }
+               $api_hosts = $this->User->getAPIHosts();
+               $this->UserAPIHosts->DataSource = array_combine($api_hosts, $api_hosts);
+               $this->UserAPIHosts->SelectedValue = $this->User->getDefaultAPIHost();
+               $this->UserAPIHosts->dataBind();
+               if (count($api_hosts) === 1) {
+                       $this->UserAPIHostsContainter->Visible = false;
+               }
+       }
+
+
        public function onPreRender($param) {
                parent::onPreRender($param);
                $this->web_config = $this->Application->getModule('web_config')->getConfig();
        }
+
+       public function setAPIHost($sender, $param) {
+               $api_host = $this->UserAPIHosts->SelectedValue;
+               if (!empty($api_host)) {
+                       $this->User->setDefaultAPIHost($api_host);
+                       $this->getResponse()->reload();
+               }
+       }
 }
 ?>
index e93aa840fecb3985ac0cbf9ed9c304a509785018..2bc50e70dda0edf8731dbe44be7c31ffd78ebdff 100644 (file)
                                <span id="msg_envelope" class="w3-tag w3-large w3-green w3-text-white w3-right w3-padding-small w3-margin-top w3-margin-right" style="cursor: pointer;<%=$this->User->isInRole(WebUserRoles::ADMIN) === false ? 'display: none' : ''%>" title="<%[ Display messages log window ]%>">
                                        <i class="fas fa-envelope w3-large"></i>
                                </span>
+                               <com:TLabel ID="UserAPIHostsContainter"CssClass="w3-right w3-margin-top w3-margin-right">
+                                       <%[ API host: ]%>
+                                       <com:TDropDownList
+                                               ID="UserAPIHosts"
+                                               CssClass="w3-select w3-border w3-small"
+                                               OnTextChanged="setAPIHost"
+                                               AutoPostBack="true"
+                                               Width="200px"
+                                               Height="34px"
+                                       />
+                               </com:TLabel>
                                <script type="text/javascript">
                                        var SIZE_VALUES_UNIT = '<%=(count($this->web_config) > 0 && key_exists('size_values_unit', $this->web_config['baculum'])) ? $this->web_config['baculum']['size_values_unit'] : WebConfig::DEF_SIZE_VAL_UNIT%>';
                                        var DATE_TIME_FORMAT = '<%=(count($this->web_config) > 0 && key_exists('date_time_format', $this->web_config['baculum'])) ? $this->web_config['baculum']['date_time_format'] : WebConfig::DEF_DATE_TIME_FORMAT%>';
index 9ee14b6d9d0cf3d3a59acd7c7144d7dd7adf3d5e..4a1d24ea5cc322aa55520fe68e46ff5215192d5c 100644 (file)
@@ -9,14 +9,26 @@
        <h3 class="w3-margin-left">[ClientId <%=$this->getClientId()%>] <%[ Client: ]%> <%=$this->getClientName()%></h3>
        <div class="w3-bar w3-green w3-margin-bottom">
                <a class="w3-bar-item w3-button tab_btn" href="<%=$this->Service->constructUrl('ClientList')%>"><i class="fa fa-angle-left"></i></a>
-               <button id="btn_client_actions" type="button" class="w3-bar-item w3-button tab_btn w3-grey" onclick="W3Tabs.open(this.id, 'client_actions'); clear_node('#new_client div.directive_value');"><%[ Actions ]%></button>
-               <button id="btn_client_jobs" type="button" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'client_jobs'); clear_node('#new_client  div.directive_value');oJobForClientList.table.responsive.recalc();"><%[ Jobs for Client ]%></button>
-               <com:TLinkButton
+               <button id="btn_client_actions" type="button" class="w3-bar-item w3-button tab_btn w3-grey" onclick="W3Tabs.open(this.id, 'client_actions');"><%[ Actions ]%></button>
+               <button id="btn_client_jobs" type="button" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'client_jobs'); oJobForClientList.table.responsive.recalc();"><%[ Jobs for Client ]%></button>
+               <com:TActiveLinkButton
                        CssClass="w3-bar-item w3-button tab_btn"
-                       Attributes.onclick="W3Tabs.open(this.id, 'client_config'); clear_node('#new_client  div.directive_value'); return false;"
+                       Attributes.onclick="W3Tabs.open(this.id, 'client_config');"
                        Text="<%[ Configure client ]%>"
                        Visible="<%=!empty($_SESSION['dir'])%>"
+                       OnClick="setDIRClientConfig"
                />
+               <a id="btn_filedaemon_config" href="javascript:void(0)" class="w3-bar-item w3-button tab_btn" onclick="load_fd_filedaemon_config(); W3Tabs.open(this.id, 'filedaemon_config');">
+                       <%[ Configure file daemon ]%>
+               </a>
+               <com:TLabel ID="UserAPIHostsContainter" CssClass="w3-right w3-margin-right">
+                       <%[ FD API host ]%>
+                       <com:TActiveDropDownList
+                               ID="UserAPIHosts"
+                               CssClass="w3-select w3-border"
+                               Width="200px"
+                       />
+               </com:TLabel>
        </div>
        <div class="w3-container tab_item" id="client_actions">
                <com:TActiveLinkButton
                </com:TActiveLinkButton>
                <i id="status_client_loading" class="fa fa-sync w3-spin" style="display: none; vertical-align: super; margin-left: 6px;"></i> <span id="status_client_error" class="w3-text-red" style="display: none"></span>
                <div id="show_client_container">
-                       <div id="status_client_text_output" class="w3-code">
-                               <pre><com:TActiveLabel ID="ShowLog" /></pre>
+                       <div class="w3-row" style="display: flex; flex-wrap: wrap;">
+                               <div class="w3-card w3-padding w3-margin-right w3-margin-bottom details_card">
+                                       <h4><%[ General ]%></h4>
+                                       <table style="width: 90%">
+                                               <tr>
+                                                       <td><%[ Enabled ]%></td>
+                                                       <td><strong><com:TActiveLabel ID="OEnabled" /></strong></td>
+                                               </tr>
+                                               <tr>
+                                                       <td><%[ FD address ]%></td>
+                                                       <td><strong><com:TActiveLabel ID="OFDAddress" /></strong></td>
+                                               </tr>
+                                               <tr>
+                                                       <td><%[ FD port ]%></td>
+                                                       <td><strong><com:TActiveLabel ID="OFDPort" /></strong></td>
+                                               </tr>
+                                               <tr>
+                                                       <td><%[ Running jobs ]%></td>
+                                                       <td><strong><com:TActiveLabel ID="ORunningJobs" /></strong></td>
+                                               </tr>
+                                       </table>
+                               </div>
+                               <div class="w3-card w3-padding w3-margin-right w3-margin-bottom details_card">
+                                       <h4><%[ Pruning ]%></h4>
+                                       <table style="width: 90%">
+                                               <tr>
+                                                       <td class="w3-half">AutoPrune</td>
+                                                       <td><strong><com:TActiveLabel ID="OAutoPrune" /></strong></td>
+                                               </tr>
+                                               <tr>
+                                                       <td><%[ Job retention ]%></td>
+                                                       <td><strong><com:TActiveLabel ID="OJobRetention" CssClass="time" /></strong></td>
+                                               </tr>
+                                               <tr>
+                                                       <td><%[ File retention ]%></td>
+                                                       <td><strong><com:TActiveLabel ID="OFileRetention" /></strong></td>
+                                               </tr>
+                                       </table>
+                               </div>
                        </div>
                </div>
                <div id="status_client_container" style="display: none">
@@ -838,10 +887,68 @@ $(function() {
 </script>
        <div class="w3-container tab_item" id="client_config" style="display: none">
                <com:Application.Web.Portlets.BaculaConfigDirectives
-                       ID="ClientConfig"
+                       ID="DIRClientConfig"
                        ComponentType="dir"
                        ResourceType="Client"
                        ShowCancelButton="false"
                />
        </div>
+       <div class="w3-container tab_item" id="filedaemon_config" style="display: none">
+               <com:TCallback ID="LoadFileDaemonConfig" OnCallback="loadFDFileDaemonConfig" />
+               <script>
+function load_fd_filedaemon_config() {
+       var cb = <%=$this->LoadFileDaemonConfig->ActiveControl->Javascript%>;
+       cb.dispatch();
+}
+               </script>
+               <com:TCallback ID="LoadFileDaemonResourcesConfig" OnCallback="loadFDResourcesConfig" />
+               <script>
+function load_fd_res_config(resource) {
+       var cb = <%=$this->LoadFileDaemonResourcesConfig->ActiveControl->Javascript%>;
+       cb.setCallbackParameter(resource);
+       cb.dispatch();
+}
+               </script>
+               <div class="w3-row w3-margin-bottom">
+                       <a href="javascript:void(0)" onclick="load_fd_filedaemon_config('FileDaemon'); W3SubTabs.open('filedaemon_filedaemon_config_btn', 'filedaemon_filedaemon_config_form');">
+                               <div id="filedaemon_filedaemon_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding w3-border-red">File Daemon</div>
+                        </a>
+                       <a href="javascript:void(0)" onclick="load_fd_res_config('Director'); W3SubTabs.open('filedaemon_director_config_btn', 'filedaemon_resources_config_form');">
+                               <div id="filedaemon_director_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Director</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_fd_res_config('Messages'); W3SubTabs.open('filedaemon_messages_config_btn', 'filedaemon_resources_config_form');">
+                               <div id="filedaemon_messages_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Messages</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_fd_res_config('Schedule'); W3SubTabs.open('filedaemon_schedule_config_btn', 'filedaemon_resources_config_form');">
+                               <div id="filedaemon_schedule_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Schedule</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_fd_res_config('Console'); W3SubTabs.open('filedaemon_console_config_btn', 'filedaemon_resources_config_form');">
+                               <div id="filedaemon_console_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Console</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_fd_res_config('Statistics'); W3SubTabs.open('filedaemon_statistics_config_btn', 'filedaemon_resources_config_form');">
+                               <div id="filedaemon_statistics_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Statistics</div>
+                       </a>
+               </div>
+               <div id="filedaemon_filedaemon_config_form" class="subtab_item">
+                       <com:TActiveLabel
+                               ID="FDFileDaemonConfigErr"
+                               Text="<%[ There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration. ]%>"
+                               CssClass="w3-text-red"
+                               Display="None"
+                       />
+                       <com:Application.Web.Portlets.BaculaConfigDirectives
+                               ID="FDFileDaemonConfig"
+                               ComponentType="fd"
+                               ResourceType="FileDaemon"
+                               ShowCancelButton="false"
+                       />
+               </div>
+               <div id="filedaemon_resources_config_form" class="subtab_item" style="display: none">
+                       <com:Application.Web.Portlets.BaculaConfigResourceList
+                               ID="FileDaemonResourcesConfig"
+                               ComponentType="fd"
+                               ResourceList="<%=[[ 'name' => 'Name', 'label' => 'Name' ]]%>"
+                       />
+               </div>
+       </div>
 </com:TContent>
index a7b4ac161ff9a70f8d705d17d8caba80baf87ab3..bd627383e4cf5a09724bc08edd3d76200271a48c 100644 (file)
@@ -20,6 +20,7 @@
  * Bacula(R) is a registered trademark of Kern Sibbald.
  */
 
+Prado::using('System.Web.UI.ActiveControls.TActiveDropDownList');
 Prado::using('System.Web.UI.ActiveControls.TActiveLabel');
 Prado::using('System.Web.UI.ActiveControls.TActiveLinkButton');
 Prado::using('System.Web.UI.ActiveControls.TCallback');
@@ -38,6 +39,7 @@ class ClientView extends BaculumWebPage {
 
        const CLIENTID = 'ClientId';
        const CLIENT_NAME = 'ClientName';
+       const CLIENT_ADDRESS = 'ClientAddress';
 
        public function onInit($param) {
                parent::onInit($param);
@@ -60,31 +62,127 @@ class ClientView extends BaculumWebPage {
                }
                $this->setClientId($clientid);
                $clientshow = $this->getModule('api')->get(
-                       array('clients', $clientid, 'show'),
+                       ['clients', $clientid, 'show', '?output=json'],
                        null,
                        true
                );
                if ($clientshow->error === 0) {
-                       $this->ShowLog->Text = implode(PHP_EOL, $clientshow->output);
+                       if (property_exists($clientshow->output, 'enabled')) {
+                               $this->OEnabled->Text = $clientshow->output->enabled == 1 ? Prado::localize('Yes') : Prado::localize('No');
+                       }
+                       if (property_exists($clientshow->output, 'address')) {
+                               $this->setClientAddress($clientshow->output->address);
+                               $this->OFDAddress->Text = $clientshow->output->address;
+                       }
+                       if (property_exists($clientshow->output, 'fdport')) {
+                               $this->OFDPort->Text = $clientshow->output->fdport;
+                       }
+                       if (property_exists($clientshow->output, 'maxjobs') && property_exists($clientshow->output, 'numjobs')) {
+                               $this->ORunningJobs->Text = $clientshow->output->numjobs . '/' . $clientshow->output->maxjobs;
+                       }
+                       if (property_exists($clientshow->output, 'autoprune')) {
+                               $this->OAutoPrune->Text = $clientshow->output->autoprune = 1 ? Prado::localize('Yes') : Prado::localize('No');
+                       }
+                       if (property_exists($clientshow->output, 'jobretention')) {
+                               $this->OJobRetention->Text = $clientshow->output->jobretention;
+                       }
+                       if (property_exists($clientshow->output, 'fileretention')) {
+                               $this->OFileRetention->Text = $clientshow->output->fileretention;
+                       }
                }
-               $client = $this->getModule('api')->get(
-                       array('clients', $clientid)
-               );
-               if ($client->error === 0) {
-                       $this->setClientName($client->output->name);
+               if (property_exists($clientshow->output, 'name')) {
+                       // set name from client show
+                       $this->setClientName($clientshow->output->name);
+               } else {
+                       // set name from catalog data request
+                       $client = $this->getModule('api')->get(
+                               array('clients', $clientid)
+                       );
+                       if ($client->error === 0) {
+                               $this->setClientName($client->output->name);
+                       }
                }
+               $this->setAPIHosts();
        }
 
-       public function onPreRender($param) {
-               parent::onPreRender($param);
-               if ($this->IsCallBack || $this->IsPostBack) {
-                       return;
-               }
+       public function setDIRClientConfig($sender, $param) {
+               $this->FDFileDaemonConfig->unloadDirectives();
                if (!empty($_SESSION['dir'])) {
-                       $this->ClientConfig->setComponentName($_SESSION['dir']);
-                       $this->ClientConfig->setResourceName($this->getClientName());
-                       $this->ClientConfig->setLoadValues(true);
-                       $this->ClientConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+                       $this->DIRClientConfig->setComponentName($_SESSION['dir']);
+                       $this->DIRClientConfig->setResourceName($this->getClientName());
+                       $this->DIRClientConfig->setLoadValues(true);
+                       $this->DIRClientConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+               }
+       }
+
+       private function setAPIHosts() {
+               $def_host = null;
+               $api_hosts = $this->getModule('host_config')->getConfig();
+               $user_api_hosts = $this->User->getAPIHosts();
+               $client_address = $this->getClientAddress();
+               foreach ($api_hosts as $name => $attrs) {
+                       if (in_array($name, $user_api_hosts) && $attrs['address'] === $client_address) {
+                               $def_host = $name;
+                               break;
+                       }
+               }
+               $this->UserAPIHosts->DataSource = array_combine($user_api_hosts, $user_api_hosts);
+               if ($def_host) {
+                       $this->UserAPIHosts->SelectedValue = $def_host;
+               } else {
+                       $this->UserAPIHosts->SelectedValue = $this->User->getDefaultAPIHost();
+               }
+               $this->UserAPIHosts->dataBind();
+               if (count($user_api_hosts) === 1) {
+                       $this->UserAPIHostsContainter->Visible = false;
+               }
+       }
+
+       private function getFDName() {
+               $fdname = null;
+               if (!$this->User->isUserAPIHost($this->UserAPIHosts->SelectedValue)) {
+                       // Validation error. Somebody manually modified select values
+                       return $fdname;
+               }
+               $result = $this->getModule('api')->get(['config'], $this->UserAPIHosts->SelectedValue);
+               if ($result->error === 0) {
+                       for ($i = 0; $i < count($result->output); $i++) {
+                               if ($result->output[$i]->component_type === 'fd' && $result->output[$i]->state) {
+                                       $fdname = $result->output[$i]->component_name;
+                               }
+
+                       }
+               }
+               return $fdname;
+       }
+
+       public function loadFDFileDaemonConfig($sender, $param) {
+               $this->DIRClientConfig->unloadDirectives();
+               $component_name = $this->getFDName();
+               if (!is_null($component_name)) {
+                       $this->FDFileDaemonConfigErr->Display = 'None';
+                       $this->FDFileDaemonConfig->setHost($this->UserAPIHosts->SelectedValue);
+                       $this->FDFileDaemonConfig->setComponentName($component_name);
+                       $this->FDFileDaemonConfig->setResourceName($component_name);
+                       $this->FDFileDaemonConfig->setLoadValues(true);
+                       $this->FDFileDaemonConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+               } else {
+                       $this->FDFileDaemonConfigErr->Display = 'Dynamic';
+               }
+       }
+
+       public function loadFDResourcesConfig($sender, $param) {
+               $resource_type = $param->getCallbackParameter();
+               $this->DIRClientConfig->unloadDirectives();
+               $this->FDFileDaemonConfig->unloadDirectives();
+               $component_name = $this->getFDName();
+               if (!is_null($component_name) && !empty($resource_type)) {
+                       $this->FileDaemonResourcesConfig->setHost($this->UserAPIHosts->SelectedValue);
+                       $this->FileDaemonResourcesConfig->setResourceType($resource_type);
+                       $this->FileDaemonResourcesConfig->setComponentName($component_name);
+                       $this->FileDaemonResourcesConfig->loadResourceListTable();
+               } else {
+                       $this->FileDaemonResourcesConfig->showError(true);
                }
        }
 
@@ -127,6 +225,24 @@ class ClientView extends BaculumWebPage {
                return $this->getViewState(self::CLIENT_NAME);
        }
 
+       /**
+        * Set client address.
+        *
+        * @return none;
+        */
+       public function setClientAddress($address) {
+               $this->setViewState(self::CLIENT_ADDRESS, $address);
+       }
+
+       /**
+        * Get client address.
+        *
+        * @return string address
+        */
+       public function getClientAddress() {
+               return $this->getViewState(self::CLIENT_ADDRESS);
+       }
+
        public function status($sender, $param) {
                $raw_status = $this->getModule('api')->get(
                        ['clients', $this->getClientId(), 'status']
index 7d8a2bf6384c6d919471f79a2a4cea82ef9db3cb..af33052d2aa4ec7fe6a9fc05f33ecb175f541adb 100644 (file)
@@ -61,7 +61,7 @@ class NewResource extends BaculumWebPage {
                        $this->setComponentName($component_name);
                        $this->setResourceType($resource_type);
                        // Non-admin can configure only host assigned to him
-                       $this->NewResource->setHost($this->User->getAPIHosts());
+                       $this->NewResource->setHost($this->User->getDefaultAPIHost());
                        $this->NewResource->setComponentType($component_type);
                        $this->NewResource->setComponentName($component_name);
                        $this->NewResource->setResourceType($resource_type);
@@ -77,8 +77,9 @@ class NewResource extends BaculumWebPage {
        public function setHosts() {
                $config = $this->getModule('host_config')->getConfig();
                $hosts = array('' => Prado::localize('Please select host'));
+               $user_api_hosts = $this->User->getAPIHosts();
                foreach ($config as $host => $vals) {
-                       if ($host !== $this->User->getAPIHosts()) {
+                       if (!in_array($host, $user_api_hosts)) {
                                continue;
                        }
                        $item = "Host: $host, Address: {$vals['address']}, Port: {$vals['port']}";
index 6d24406f02095828deba591704fdeeaf976188c3..e44d7dc7ed184076dfbf57129014bfce86c4b68e 100644 (file)
                <button type="button" class="w3-button w3-green" onclick="document.location.href='<%=$this->Service->constructUrl('ScheduleStatusList')%>';"><i class="fa fa-clock"></i> &nbsp;<%[ Schedule status ]%></button>
        </div>
        <div class="w3-container">
-                       <table id="schedule_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom">
-                               <thead>
-                                       <tr>
-                                               <th></th>
-                                               <th><%[ Name ]%></th>
-                                               <th class="w3-center"><%[ Actions ]%></th>
-                                       </tr>
-                               </thead>
-                               <tbody id="schedule_list_body"></tbody>
-                               <tfoot>
-                                       <tr>
-                                               <th></th>
-                                               <th><%[ Name ]%></th>
-                                               <th class="w3-center"><%[ Actions ]%></th>
-                                       </tr>
-                               </tfoot>
-                       </table>
+               <table id="schedule_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom">
+                       <thead>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Name ]%></th>
+                                       <th class="w3-center"><%[ Actions ]%></th>
+                               </tr>
+                       </thead>
+                       <tbody id="schedule_list_body"></tbody>
+                       <tfoot>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Name ]%></th>
+                                       <th class="w3-center"><%[ Actions ]%></th>
+                               </tr>
+                       </tfoot>
                </table>
        </div>
 <script type="text/javascript">
index c774ef7223193ff7b68f5731109340144133b4fb..1f9d167368fa24e69cfe287848db9c2dc38d2e9c 100644 (file)
                                                        </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
                                                </div>
                                                <div class="w3-row w3-section">
-                                                       <div class="w3-col w3-third"><com:TLabel ForControl="GetUsersDefaultAPIHost" Text="<%[ Default API host for imported users: ]%>" /></div>
+                                                       <div class="w3-col w3-third"><com:TLabel ForControl="GetUsersDefaultAPIHosts" Text="<%[ Default API hosts for imported users: ]%>" /></div>
                                                        <div class="w3-half">
-                                                               <com:TActiveDropDownList
-                                                                       ID="GetUsersDefaultAPIHost"
+                                                               <com:TActiveListBox
+                                                                       ID="GetUsersDefaultAPIHosts"
+                                                                       SelectionMode="Multiple"
+                                                                       Rows="6"
                                                                        CssClass="w3-select w3-border"
                                                                        AutoPostBack="false"
                                                                />
+                                                               <p class="w3-text-black" style="margin: 0 16px 0 0"><%[ Use CTRL + left-click to multiple item selection ]%></p>
                                                        </div>
                                                </div>
                                                <div class="w3-row w3-section" title="<%[ Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.* ]%>">
@@ -1005,7 +1008,7 @@ oLdapUserSecurity.init();
                                        <th class="w3-center"><%[ Description ]%></th>
                                        <th class="w3-center"><%[ E-mail ]%></th>
                                        <th class="w3-center"><%[ Roles ]%></th>
-                                       <th class="w3-center"><%[ API host ]%></th>
+                                       <th class="w3-center"><%[ API hosts ]%></th>
                                        <th class="w3-center"><%[ IP address restrictions ]%></th>
                                        <th class="w3-center"><%[ Enabled ]%></th>
                                        <th class="w3-center"><%[ Action ]%></th>
@@ -1020,7 +1023,7 @@ oLdapUserSecurity.init();
                                        <th class="w3-center"><%[ Description ]%></th>
                                        <th class="w3-center"><%[ E-mail ]%></th>
                                        <th class="w3-center"><%[ Roles ]%></th>
-                                       <th class="w3-center"><%[ API host ]%></th>
+                                       <th class="w3-center"><%[ API hosts ]%></th>
                                        <th class="w3-center"><%[ IP address restrictions ]%></th>
                                        <th class="w3-center"><%[ Enabled ]%></th>
                                        <th class="w3-center"><%[ Action ]%></th>
@@ -1250,7 +1253,7 @@ var oUsers = {
                        '<%=$this->UserEmail->ClientID%>',
                        '<%=$this->UserPassword->ClientID%>',
                        '<%=$this->UserRoles->ClientID%>',
-                       '<%=$this->UserAPIHost->ClientID%>',
+                       '<%=$this->UserAPIHosts->ClientID%>',
                        '<%=$this->UserIps->ClientID%>'
                ].forEach(function(id) {
                        document.getElementById(id).value = '';
@@ -1391,13 +1394,16 @@ $(function() {
                                                </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
                                        </div>
                                        <div class="w3-row w3-section">
-                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserAPIHost" Text="<%[ API host: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserAPIHosts" Text="<%[ API hosts: ]%>" CssClass="w3-xlarge"/></div>
                                                <div class="w3-half">
-                                                       <com:TActiveDropDownList
-                                                               ID="UserAPIHost"
+                                                       <com:TActiveListBox
+                                                               ID="UserAPIHosts"
+                                                               SelectionMode="Multiple"
+                                                               Rows="6"
                                                                CssClass="w3-select w3-border"
                                                                AutoPostBack="false"
                                                        />
+                                                       <p class="w3-text-black" style="margin: 0 16px 0 0"><%[ Use CTRL + left-click to multiple item selection ]%></p>
                                                </div>
                                        </div>
                                        <div class="w3-row w3-section">
index 23983e96854ed8bb4bc4fe2c810a68c6c5d12dfb..a773d46875d432dbf6565428a45a3bd7c05410dd 100644 (file)
@@ -188,7 +188,7 @@ class Security extends BaculumWebPage {
         */
        public function initUserWindow() {
                // set API hosts
-               $this->setAPIHosts($this->UserAPIHost);
+               $this->setAPIHosts($this->UserAPIHosts, null, false);
 
                // set roles
                $this->setRoles($this->UserRoles);
@@ -220,11 +220,14 @@ class Security extends BaculumWebPage {
         *
         * @param object $control control which contains API host list
         * @param mixed $def_val default value or null if no default value to set
+        * @param boolean determines if add first blank item
         * @return none
         */
-       private function setAPIHosts($control, $def_val = null) {
+       private function setAPIHosts($control, $def_val = null, $add_blank_item = true) {
                $api_hosts = array_keys($this->getModule('host_config')->getConfig());
-               array_unshift($api_hosts, '');
+               if ($add_blank_item) {
+                       array_unshift($api_hosts, '');
+               }
                $control->DataSource = array_combine($api_hosts, $api_hosts);
                if ($def_val) {
                        $control->SelectedValue = $def_val;
@@ -265,6 +268,8 @@ class Security extends BaculumWebPage {
                        $this->UserDescription->Text = $config['description'];
                        $this->UserEmail->Text = $config['email'];
                        $this->UserPassword->Text = '';
+
+                       // set roles
                        $selected_indices = [];
                        $roles = explode(',', $config['roles']);
                        for ($i = 0; $i < $this->UserRoles->getItemCount(); $i++) {
@@ -273,7 +278,16 @@ class Security extends BaculumWebPage {
                                }
                        }
                        $this->UserRoles->setSelectedIndices($selected_indices);
-                       $this->UserAPIHost->SelectedValue = $config['api_hosts'];
+
+                       $selected_indices = [];
+                       $api_hosts = $config['api_hosts'];
+                       for ($i = 0; $i < $this->UserAPIHosts->getItemCount(); $i++) {
+                               if (in_array($this->UserAPIHosts->Items[$i]->Value, $api_hosts)) {
+                                       $selected_indices[] = $i;
+                               }
+                       }
+
+                       $this->UserAPIHosts->setSelectedIndices($selected_indices);
                        $this->UserIps->Text = $config['ips'];
                        $this->UserEnabled->Checked = ($config['enabled'] == 1);
                }
@@ -316,6 +330,7 @@ class Security extends BaculumWebPage {
                $config['description'] = $this->UserDescription->Text;
                $config['email'] = $this->UserEmail->Text;
 
+               // set roles config values
                $selected_indices = $this->UserRoles->getSelectedIndices();
                $roles = [];
                foreach ($selected_indices as $indice) {
@@ -326,7 +341,18 @@ class Security extends BaculumWebPage {
                        }
                }
                $config['roles'] = implode(',', $roles);
-               $config['api_hosts'] = $this->UserAPIHost->SelectedValue;
+
+               // set API hosts config values
+               $selected_indices = $this->UserAPIHosts->getSelectedIndices();
+               $api_hosts = [];
+               foreach ($selected_indices as $indice) {
+                       for ($i = 0; $i < $this->UserAPIHosts->getItemCount(); $i++) {
+                               if ($i === $indice) {
+                                       $api_hosts[] = $this->UserAPIHosts->Items[$i]->Value;
+                               }
+                       }
+               }
+               $config['api_hosts'] = $api_hosts;
                $config['ips'] = $this->trimIps($this->UserIps->Text);
                $config['enabled'] = $this->UserEnabled->Checked ? 1 : 0;
                $result = $this->getModule('user_config')->setUserConfig($username, $config);
@@ -600,7 +626,7 @@ class Security extends BaculumWebPage {
                        $this->setRoles($this->GetUsersDefaultRole, WebUserRoles::NORMAL);
 
                        // set API hosts
-                       $this->setAPIHosts($this->GetUsersDefaultAPIHost, HostConfig::MAIN_CATALOG_HOST);
+                       $this->setAPIHosts($this->GetUsersDefaultAPIHosts, HostConfig::MAIN_CATALOG_HOST, false);
                }
 
                $params = $this->getBasicParams();
@@ -786,7 +812,7 @@ class Security extends BaculumWebPage {
                        $this->setRoles($this->GetUsersDefaultRole, WebUserRoles::NORMAL);
 
                        // set API hosts
-                       $this->setAPIHosts($this->GetUsersDefaultAPIHost, HostConfig::MAIN_CATALOG_HOST);
+                       $this->setAPIHosts($this->GetUsersDefaultAPIHosts, HostConfig::MAIN_CATALOG_HOST, false);
                }
 
                $ldap = $this->getModule('ldap');
@@ -1075,7 +1101,16 @@ class Security extends BaculumWebPage {
                $roles = implode(',', $role_list);
 
                // Get default API hosts for imported users
-               $api_hosts = $this->GetUsersDefaultAPIHost->SelectedValue;
+               $selected_indices = $this->GetUsersDefaultAPIHosts->getSelectedIndices();
+               $api_host_list = [];
+               foreach ($selected_indices as $indice) {
+                       for ($i = 0; $i < $this->GetUsersDefaultAPIHosts->getItemCount(); $i++) {
+                               if ($i === $indice) {
+                                       $api_host_list[] = $this->GetUsersDefaultAPIHosts->Items[$i]->Value;
+                               }
+                       }
+               }
+               $api_hosts = implode(',', $api_host_list);
 
                // Get default IP address restrictions for imported users
                $ips = $this->trimIps($this->GetUsersDefaultIps->Text);
@@ -1242,7 +1277,7 @@ class Security extends BaculumWebPage {
                        $this->ConsoleConfig->setLoadValues(false);
                        $this->getCallbackClient()->callClientFunction('oBaculaConfigSection.show_sections', [true]);
                }
-               $this->ConsoleConfig->setHost($this->User->getAPIHosts());
+               $this->ConsoleConfig->setHost($this->User->getDefaultAPIHost());
                $this->ConsoleConfig->setComponentName($_SESSION['dir']);
                $this->ConsoleConfig->raiseEvent('OnDirectiveListLoad', $this, null);
        }
@@ -1259,7 +1294,7 @@ class Security extends BaculumWebPage {
        public function removeConsoles($sender, $param) {
                $consoles = explode('|', $param->getCallbackParameter());
                $res = new BaculaConfigResources();
-               $config = $res->getConfigData($this->User->getAPIHosts(), 'dir');
+               $config = $res->getConfigData($this->User->getDefaultAPIHost(), 'dir');
                for ($i = 0; $i < count($consoles); $i++) {
                        $res->removeResourceFromConfig(
                                $config,
@@ -1270,7 +1305,7 @@ class Security extends BaculumWebPage {
                $this->getModule('api')->set(
                        array('config', 'dir'),
                        array('config' => json_encode($config)),
-                       $this->User->getAPIHosts(),
+                       $this->User->getDefaultAPIHost(),
                        false
                );
 
diff --git a/gui/baculum/protected/Web/Pages/SelectAPIHost.page b/gui/baculum/protected/Web/Pages/SelectAPIHost.page
new file mode 100644 (file)
index 0000000..8acfcf9
--- /dev/null
@@ -0,0 +1,12 @@
+<%@ MasterClass="Application.Web.Layouts.Simple" Theme="Baculum-v2"%>
+<com:TContent ID="Main">
+       <div style="width: 100%; height: 100%;">
+               <com:TPanel ID="SelectAPIHostForm" CssClass="w3-display-middle w3-center" Style="width: 100%; max-width: 440px">
+                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/logo_xl.png" alt="Baculum - The Bacula web interface" class="w3-block" style="margin-bottom: 10px"/>
+                       <h3><%[ Please select API host ]%></h3>
+                       <div class="w3-section">
+                               <label for="<%=$this->UserAPIHosts->ClientID%>" class="w3-show-inline-block" style="width: 95px"><%[ API host: ]%></label> <com:TDropDownList ID="UserAPIHosts" CssClass="w3-input w3-border w3-show-inline-block" Style="width: 335px" AutoPostBack="true" OnTextChanged="setAPIHost" />
+                       </div>
+               </com:TPanel>
+       </div>
+</com:TContent>
diff --git a/gui/baculum/protected/Web/Pages/SelectAPIHost.php b/gui/baculum/protected/Web/Pages/SelectAPIHost.php
new file mode 100644 (file)
index 0000000..adb67a0
--- /dev/null
@@ -0,0 +1,53 @@
+<?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.Web.Class.BaculumWebPage');
+
+/**
+ * Select API host page.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Page
+ * @package Baculum Web
+ */
+class SelectAPIHost extends BaculumWebPage {
+
+       public function onInit($param) {
+               parent::onInit($param);
+               if ($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
+               $api_hosts = $this->User->getAPIHosts();
+               array_unshift($api_hosts, '');
+               $this->UserAPIHosts->DataSource = array_combine($api_hosts, $api_hosts);
+               $this->UserAPIHosts->dataBind();
+       }
+
+       public function setAPIHost($sender, $param) {
+               $api_host = $this->UserAPIHosts->SelectedValue;
+               if (!empty($api_host)) {
+                       $this->User->setDefaultAPIHost($api_host);
+                       $this->goToDefaultPage();
+               }
+       }
+}
+?>
index b29b26a334528ab3cf66848284cbd76b332d8bdf..f37824910919fca40eff8a02c2e74869a8a060ea 100644 (file)
                        Visible="<%=!empty($_SESSION['sd'])%>"
                        OnClick="setStorage"
                />
-               <com:TActiveLinkButton
-                       CssClass="w3-bar-item w3-button tab_btn"
-                       Attributes.onclick="W3Tabs.open(this.id, 'configure_devices');"
-                       Text="<%[ Configure devices ]%>"
-                       Visible="<%=!empty($_SESSION['sd'])%>"
-                       OnClick="setDevices"
-               />
-               <com:TActiveLinkButton
-                       CssClass="w3-bar-item w3-button tab_btn"
-                       Attributes.onclick="W3Tabs.open(this.id, 'configure_autochanger');clear_node('#configure_storage  div.directive_value');"
-                       Text="<%[ Configure autochanger ]%>"
-                       Visible="<%=!empty($_SESSION['sd']) && $this->getIsAutochanger()%>"
-                       OnClick="setAutochanger"
-               />
+               <a id="btn_storagedaemon_config" href="javascript:void(0)" class="w3-bar-item w3-button tab_btn" onclick="load_sd_storagedaemon_config(); W3Tabs.open(this.id, 'configure_storage_daemon');">
+                       <%[ Configure storage daemon ]%>
+               </a>
                <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()%>"
+                       Visible="<%=$this->getIsAutochanger()%>"
                        OnCallback="loadAutochanger"
                />
+               <com:TLabel ID="UserAPIHostsContainter" CssClass="w3-right w3-margin-right">
+                       <%[ SD API host ]%>
+                       <com:TActiveDropDownList
+                               ID="UserAPIHosts"
+                               CssClass="w3-select w3-border"
+                               Width="200px"
+                       />
+               </com:TLabel>
        </div>
        <div class="w3-container tab_item" id="storage_actions">
                <com:TValidationSummary
@@ -56,7 +53,7 @@
                        ClientSide.OnLoading="$('#status_storage_loading').show();$('#status_storage_error').hide();"
                        ClientSide.OnSuccess="$('#status_storage_loading').hide(); $('#show_storage_container').hide();$('#status_storage_container').show();oGraphicalStorageStatus.set_refresh_timeout(document.getElementById('status_storage_refresh_interval').value);"
                        ClientSide.OnFailure="$('#status_storage_loading').hide();status_storage_show_error(parameter);"
-                       Attributes.onclick="hide_action_text_output(event);"
+                       Attributes.onclick="hide_first_tab_containers(event);"
                >
                        <prop:Text><%=Prado::localize('Status storage')%> &nbsp;<i class="fa fa-file-medical-alt"></i></prop:Text>
                </com:TActiveLinkButton>
@@ -143,46 +140,82 @@ var oStorageActions = {
                </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"
-                                       />
-                               </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"
-                                       />
-                               </div>
+                       <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"
+                               />
+                       </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"
+                               />
+                       </div>
                </com:TActivePanel>
                <div id="storage_action_text_output" class="w3-code" style="display: none; clear: both;">
                        <pre id="storage_action_log"></pre>
                </div>
+               <div id="storage_info_cards" class="w3-row w3-margin-top" style="display: flex; flex-wrap: wrap;">
+                       <div class="w3-card w3-padding w3-margin-right w3-margin-bottom details_card">
+                               <h4><%[ General ]%></h4>
+                               <table style="width: 90%">
+                                       <tr>
+                                               <td><%[ SD address ]%></td>
+                                               <td><strong><com:TActiveLabel ID="OSDAddress" /></strong></td>
+                                       </tr>
+                                       <tr>
+                                               <td><%[ SD port ]%></td>
+                                               <td><strong><com:TActiveLabel ID="OSDPort" /></strong></td>
+                                       </tr>
+                                       <tr>
+                                               <td><%[ Running jobs ]%></td>
+                                               <td><strong><com:TActiveLabel ID="ORunningJobs" /></strong></td>
+                                       </tr>
+                               </table>
+                       </div>
+                       <div class="w3-card w3-padding w3-margin-right w3-margin-bottom details_card">
+                               <h4><%[ Device ]%></h4>
+                               <table style="width: 90%">
+                                       <tr>
+                                               <td class="w3-half"><%[ Device name ]%></td>
+                                               <td><strong><com:TActiveLabel ID="ODeviceName" /></strong></td>
+                                       </tr>
+                                       <tr>
+                                               <td><%[ Media type ]%></td>
+                                               <td><strong><com:TActiveLabel ID="OMediaType" CssClass="time" /></strong></td>
+                                       </tr>
+                                       <tr>
+                                               <td><%[ Autochanger ]%></td>
+                                               <td><strong><com:TActiveLabel ID="OAutoChanger" /></strong></td>
+                                       </tr>
+                               </table>
+                       </div>
+               </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. ]%>">
                                <span style="line-height: 41px"><%[ Refresh interval (sec.): ]%></span> <input type="text" id="status_storage_refresh_interval" class="w3-input w3-border w3-right w3-margin-left" value="10" style="width: 50px"/>
@@ -1569,9 +1602,10 @@ var oGraphicalStorageStatus = {
        }
 };
 
-function hide_action_text_output(e) {
+function hide_first_tab_containers(e) {
        if (e.hasOwnProperty('originalEvent') && e.originalEvent.type == 'click') {
                $('#storage_action_text_output').slideUp('fast');
+               $('#storage_info_cards').slideUp('fast');
        }
 }
 
@@ -1601,36 +1635,72 @@ MonitorParams = {
        </div>
        <div class="w3-container tab_item" id="configure_storage" style="display: none">
                <com:Application.Web.Portlets.BaculaConfigDirectives
-                       ID="StorageConfig"
+                       ID="DIRStorageConfig"
                        ComponentType="dir"
                        ResourceType="Storage"
                        ShowCancelButton="false"
                />
        </div>
-       <div class="w3-container tab_item" id="configure_devices" style="display: none">
-               <com:TActiveRepeater ID="Devices">
-                       <prop:HeaderTemplate>
-                               <table id="storage_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom">
-                                       <thead>
-                                               <tr>
-                                                       <th><%[ Device name  ]%></th>
-                                                       <th><%[ Autochanger ]%></th>
-                                                       <th class="w3-center"><%[ Actions ]%></th>
-                                               </tr>
-                                       </thead>
-                       </prop:HeaderTemplate>
-                       <prop:ItemTemplate>
-                               <tr>
-                                       <td><%#$this->Data%></td>
-                                       <td><%=$this->TemplateControl->IsAutochanger ? $this->TemplateControl->getDeviceName() : '-'%></td>
-                                       <td class="w3-center"><button type="button" class="w3-button w3-green" onclick="document.location.href='<%=$this->Service->constructUrl('DeviceView', array('storageid' => $this->TemplateControl->getStorageId(), 'device' => $this->Data))%>';"><i class="fa fa-list-ul"></i> &nbsp;<%[ Details ]%></button></td>
-                               </tr>
-                       </prop:ItemTemplate>
-                       <prop:FooterTemplate>
-                               </table>
-                       </prop:FooterTemplate>
-               </com:TActiveRepeater>
-               </table>
+       <div class="w3-container tab_item" id="configure_storage_daemon" style="display: none">
+               <com:TCallback ID="LoadStorageDaemonConfig" OnCallback="loadSDStorageDaemonConfig" />
+               <script>
+function load_sd_storagedaemon_config() {
+       var cb = <%=$this->LoadStorageDaemonConfig->ActiveControl->Javascript%>;
+       cb.dispatch();
+}
+               </script>
+               <com:TCallback ID="LoadStorageDaemonResourcesConfig" OnCallback="loadSDResourcesConfig" />
+               <script>
+function load_sd_res_config(resource) {
+       var cb = <%=$this->LoadStorageDaemonResourcesConfig->ActiveControl->Javascript%>;
+       cb.setCallbackParameter(resource);
+       cb.dispatch();
+}
+               </script>
+               <div class="w3-row w3-margin-bottom">
+                       <a href="javascript:void(0)" onclick="load_sd_storagedaemon_config(); W3SubTabs.open('sd_storagedaemon_config_btn', 'sd_storagedaemon_config_form');">
+                               <div id="sd_storagedaemon_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding w3-border-red">Storage</div>
+                        </a>
+                       <a href="javascript:void(0)" onclick="load_sd_res_config('Director'); W3SubTabs.open('storagedaemon_director_config_btn', 'sd_resources_config_form');">
+                               <div id="storagedaemon_director_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Director</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_sd_res_config('Device'); W3SubTabs.open('storagedaemon_device_config_btn', 'sd_resources_config_form');">
+                               <div id="storagedaemon_device_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Device</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_sd_res_config('Autochanger'); W3SubTabs.open('storagedaemon_autochanger_config_btn', 'sd_resources_config_form');">
+                               <div id="storagedaemon_autochanger_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Autochanger</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_sd_res_config('Messages'); W3SubTabs.open('storagedaemon_messages_config_btn', 'sd_resources_config_form');">
+                               <div id="storagedaemon_messages_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Messages</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_sd_res_config('Cloud'); W3SubTabs.open('storagedaemon_cloud_config_btn', 'sd_resources_config_form');">
+                               <div id="storagedaemon_cloud_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Cloud</div>
+                       </a>
+                       <a href="javascript:void(0)" onclick="load_sd_res_config('Statistics'); W3SubTabs.open('storagedaemon_statistics_config_btn', 'sd_resources_config_form');">
+                               <div id="storagedaemon_statistics_config_btn" class="subtab_btn w3-col m1 w3-bottombar w3-hover-light-grey w3-padding">Statistics</div>
+                       </a>
+               </div>
+               <div id="sd_storagedaemon_config_form" class="subtab_item">
+                       <com:TActiveLabel
+                               ID="SDStorageDaemonConfigErr"
+                               Text="<%[ There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration. ]%>"
+                               CssClass="w3-text-red"
+                               Display="None"
+                       />
+                       <com:Application.Web.Portlets.BaculaConfigDirectives
+                               ID="SDStorageDaemonConfig"
+                               ComponentType="sd"
+                               ResourceType="Storage"
+                               ShowCancelButton="false"
+                       />
+               </div>
+               <div id="sd_resources_config_form" class="subtab_item" style="display: none">
+                       <com:Application.Web.Portlets.BaculaConfigResourceList
+                               ID="StorageDaemonResourcesConfig"
+                               ComponentType="sd"
+                               ResourceList="<%=[[ 'name' => 'Name', 'label' => 'Name' ]]%>"
+                       />
+               </div>
        </div>
        <div class="w3-container tab_item" id="configure_autochanger" style="display: none">
                <com:Application.Web.Portlets.BaculaConfigDirectives
index cc3a96b894c46a19c8cc73ca6aace9843c49a0a1..df39ab4d1a054b49b26c4673a36ef89e6a22a942 100644 (file)
@@ -40,6 +40,7 @@ class StorageView extends BaculumWebPage {
 
        const STORAGEID = 'StorageId';
        const STORAGE_NAME = 'StorageName';
+       const STORAGE_ADDRESS = 'StorageAddress';
        const IS_AUTOCHANGER = 'IsAutochanger';
        const DEVICE_NAME = 'DeviceName';
 
@@ -64,137 +65,63 @@ class StorageView extends BaculumWebPage {
                                }
                        }
                }
-               $storage = $this->Application->getModule('api')->get(
-                       array('storages', $storageid),
+               $this->setStorageId($storageid);
+               $storageshow = $this->getModule('api')->get(
+                       ['storages', $storageid, 'show', '?output=json'],
                        null,
-                       true,
-                       self::USE_CACHE
-               )->output;
-               $this->setStorageId($storage->storageid);
-               $this->setStorageName($storage->name);
-               $is_autochanger = ($storage->autochanger == 1);
-               $this->setIsAutochanger($is_autochanger);
-               $this->Autochanger->Display = $is_autochanger ? 'Dynamic': 'None';
-               $storageshow = $this->Application->getModule('api')->get(
-                       array('storages', $storage->storageid, 'show')
-               )->output;
-               $this->setStorageDevice($storageshow);
-               $this->setDevices();
-       }
+                       true
+               );
+               if ($storageshow->error === 0) {
+                       $this->setStorageName($storageshow->output->name);
 
-       public function setStorageDevice($storageshow) {
-               /**
-                * Note, it cannot be get by api config because user can have bdirjson not configured.
-                */
-               for ($i = 0; $i < count($storageshow); $i++) {
-                       if (preg_match('/^\s+DeviceName=(?P<device>[\s\S]+)\sMediaType=/', $storageshow[$i], $match) === 1) {
-                               $this->setDeviceName($match['device']);
+                       if (property_exists($storageshow->output, 'address')) {
+                               $this->setStorageAddress($storageshow->output->address);
+                               $this->OSDAddress->Text = $storageshow->output->address;
+                       }
+                       if (property_exists($storageshow->output, 'sdport')) {
+                               $this->OSDPort->Text = $storageshow->output->sdport;
+                       }
+                       if (property_exists($storageshow->output, 'maxjobs') && property_exists($storageshow->output, 'numjobs')) {
+                               $this->ORunningJobs->Text = $storageshow->output->numjobs . '/' . $storageshow->output->maxjobs;
+                       }
+                       if (property_exists($storageshow->output, 'devicename')) {
+                               $this->setDeviceName($storageshow->output->devicename);
+                               $this->ODeviceName->Text = $storageshow->output->devicename;
+                       }
+                       if (property_exists($storageshow->output, 'mediatype')) {
+                               $this->OMediaType->Text = $storageshow->output->mediatype;
+                       }
+                       if (property_exists($storageshow->output, 'autochanger')) {
+                               $is_autochanger = ($storageshow->output->autochanger == 1);
+                               $this->setIsAutochanger($is_autochanger);
+                               $this->OAutoChanger->Text = $is_autochanger ? Prado::localize('Yes') : Prado::localize('No');
+                               $this->Autochanger->Display = $is_autochanger ? 'Dynamic': 'None';
                        }
                }
+               $this->setAPIHosts();
        }
 
-       public function setDevices() {
-               $devices = array();
-               if ($this->getIsAutochanger() && !empty($_SESSION['sd'])) {
-                       /**
-                        * NOTE: Here is called only Main API host. For storage daemons
-                        * on other hosts it can cause a problem. So far, there
-                        * is no 100% way to unambiguously determine basing on storage daemon
-                        * configuration if autochanger comes from Main or from other API host.
-                        * The problem will be if on Main host is defined autochanger
-                        * with the same name as autochanger from requested Storage here.
-                        * @TODO: Find a way to solve it.
-                        */
-                       $result = $this->Application->getModule('api')->get(
-                               array(
-                                       'config',
-                                       'sd',
-                                       'Autochanger',
-                                       $this->getDeviceName()
-                               )
-                       );
-                       if ($result->error === 0 && is_object($result->output)) {
-                               $devices = $result->output->Device;
+       private function setAPIHosts() {
+               $def_host = null;
+               $api_hosts = $this->getModule('host_config')->getConfig();
+               $user_api_hosts = $this->User->getAPIHosts();
+               $storage_address = $this->getStorageAddress();
+               foreach ($api_hosts as $name => $attrs) {
+                       if (in_array($name, $user_api_hosts) && $attrs['address'] === $storage_address) {
+                               $def_host = $name;
+                               break;
                        }
+               }
+               $this->UserAPIHosts->DataSource = array_combine($user_api_hosts, $user_api_hosts);
+               if ($def_host) {
+                       $this->UserAPIHosts->SelectedValue = $def_host;
                } else {
-                       $devices = array($this->getDeviceName());
+                       $this->UserAPIHosts->SelectedValue = $this->User->getDefaultAPIHost();
+               }
+               $this->UserAPIHosts->dataBind();
+               if (count($user_api_hosts) === 1) {
+                       $this->UserAPIHostsContainter->Visible = false;
                }
-               $this->Devices->DataSource = $devices;
-               $this->Devices->dataBind();
-       }
-
-       /**
-        * Set storage storageid.
-        *
-        * @return none;
-        */
-       public function setStorageId($storageid) {
-               $storageid = intval($storageid);
-               $this->setViewState(self::STORAGEID, $storageid, 0);
-       }
-
-       /**
-        * Get storage storageid.
-        *
-        * @return integer storageid
-        */
-       public function getStorageId() {
-               return $this->getViewState(self::STORAGEID, 0);
-       }
-
-       /**
-        * Set storage name.
-        *
-        * @return none;
-        */
-       public function setStorageName($storage_name) {
-               $this->setViewState(self::STORAGE_NAME, $storage_name);
-       }
-
-       /**
-        * Get storage name.
-        *
-        * @return string storage name
-        */
-       public function getStorageName() {
-               return $this->getViewState(self::STORAGE_NAME);
-       }
-
-       /**
-        * Set device name.
-        *
-        * @return none;
-        */
-       public function setDeviceName($device_name) {
-               $this->setViewState(self::DEVICE_NAME, $device_name);
-       }
-
-       /**
-        * Get device name.
-        *
-        * @return string device name
-        */
-       public function getDeviceName() {
-               return $this->getViewState(self::DEVICE_NAME);
-       }
-
-       /**
-        * Check if storage is autochanger
-        *
-        * @return bool true if autochanger, otherwise false
-        */
-       public function getIsAutochanger() {
-               return $this->getViewState(self::IS_AUTOCHANGER, false);
-       }
-
-       /**
-        * Set autochanger value for storage
-        *
-        * @return none;
-        */
-       public function setIsAutochanger($is_autochanger) {
-               settype($is_autochanger, 'bool');
-               $this->setViewState(self::IS_AUTOCHANGER, $is_autochanger);
        }
 
        public function status($sender, $param) {
@@ -257,6 +184,77 @@ class StorageView extends BaculumWebPage {
                $this->getCallbackClient()->callClientFunction('init_graphical_storage_status', [$storage_status]);
        }
 
+       private function getSDAPIHost() {
+               if (!$this->User->isUserAPIHost($this->UserAPIHosts->SelectedValue)) {
+                       // Validation error. Somebody manually modified select values
+                       return false;
+               }
+               return $this->UserAPIHosts->SelectedValue;
+       }
+
+       private function getSDName() {
+               if (!($host = $this->getSDAPIHost())) {
+                       return;
+               }
+               $sdname = null;
+               $result = $this->getModule('api')->get(['config'], $host);
+               if ($result->error === 0) {
+                       for ($i = 0; $i < count($result->output); $i++) {
+                               if ($result->output[$i]->component_type === 'sd' && $result->output[$i]->state) {
+                                       $sdname = $result->output[$i]->component_name;
+                               }
+
+                       }
+               }
+               return $sdname;
+       }
+
+       public function setStorage($sender, $param) {
+               $this->SDStorageDaemonConfig->unloadDirectives();
+               if (!empty($_SESSION['dir'])) {
+                       $this->DIRStorageConfig->setComponentName($_SESSION['dir']);
+                       $this->DIRStorageConfig->setResourceName($this->getStorageName());
+                       $this->DIRStorageConfig->setLoadValues(true);
+                       $this->DIRStorageConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+               }
+       }
+
+       public function loadSDStorageDaemonConfig($sender, $param) {
+               if (!($host = $this->getSDAPIHost())) {
+                       return;
+               }
+               $this->DIRStorageConfig->unloadDirectives();
+               $component_name = $this->getSDName();
+               if (!is_null($component_name)) {
+                       $this->SDStorageDaemonConfigErr->Display = 'None';
+                       $this->SDStorageDaemonConfig->setHost($host);
+                       $this->SDStorageDaemonConfig->setComponentName($component_name);
+                       $this->SDStorageDaemonConfig->setResourceName($component_name);
+                       $this->SDStorageDaemonConfig->setLoadValues(true);
+                       $this->SDStorageDaemonConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+               } else {
+                       $this->SDStorageDaemonConfigErr->Display = 'Dynamic';
+               }
+       }
+
+       public function loadSDResourcesConfig($sender, $param) {
+               if (!($host = $this->getSDAPIHost())) {
+                       return;
+               }
+               $resource_type = $param->getCallbackParameter();
+               $this->DIRStorageConfig->unloadDirectives();
+               $this->SDStorageDaemonConfig->unloadDirectives();
+               $component_name = $this->getSDName();
+               if (!is_null($component_name) && !empty($resource_type)) {
+                       $this->StorageDaemonResourcesConfig->setHost($host);
+                       $this->StorageDaemonResourcesConfig->setResourceType($resource_type);
+                       $this->StorageDaemonResourcesConfig->setComponentName($component_name);
+                       $this->StorageDaemonResourcesConfig->loadResourceListTable();
+               } else {
+                       $this->StorageDaemonResourcesConfig->showError(true);
+               }
+       }
+
        private function actionLoading($result, $out_id, $refresh_func) {
                $messages_log = $this->getModule('messages_log');
                if ($result->error === 0) {
@@ -378,14 +376,12 @@ class StorageView extends BaculumWebPage {
                        'drive' => $drive
                ];
                $query = '?' . http_build_query($params);
-               $result = $this->getModule('api')->set(
-                       [
-                               'storages',
-                               $this->getStorageId(),
-                               'umount',
-                               $query
-                       ]
-               );
+               $result = $this->getModule('api')->set([
+                       'storages',
+                       $this->getStorageId(),
+                       'umount',
+                       $query
+               ]);
                return $result;
        }
 
@@ -431,14 +427,12 @@ class StorageView extends BaculumWebPage {
                        'drive' => $drive
                ];
                $query = '?' . http_build_query($params);
-               $result = $this->getModule('api')->set(
-                       [
-                               'storages',
-                               $this->getStorageId(),
-                               'release',
-                               $query
-                       ]
-               );
+               $result = $this->getModule('api')->set([
+                       'storages',
+                       $this->getStorageId(),
+                       'release',
+                       $query
+               ]);
                return $result;
        }
 
@@ -471,19 +465,18 @@ class StorageView extends BaculumWebPage {
                }
        }
 
-       public function setStorage($sender, $param) {
-               $this->StorageConfig->setComponentName($_SESSION['sd']);
-               $this->StorageConfig->setResourceName($this->getStorageName());
-               $this->StorageConfig->setLoadValues(true);
-               $this->StorageConfig->raiseEvent('OnDirectiveListLoad', $this, null);
-       }
-
        public function loadAutochanger($sender, $param) {
-               $result = $this->getModule('api')->get([
-                       'devices',
-                       $this->getDeviceName(),
-                       'listall'
-               ]);
+               if (!($host = $this->getSDAPIHost())) {
+                       return;
+               }
+               $result = $this->getModule('api')->get(
+                       [
+                               'devices',
+                               $this->getDeviceName(),
+                               'listall'
+                       ],
+                       $host
+               );
                $cb = $this->getCallbackClient();
                if ($result->error === 0) {
                        $cb->show('drive_list_container');
@@ -527,17 +520,27 @@ class StorageView extends BaculumWebPage {
                                $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 ($host = $this->getSDAPIHost()) {
+                               $parameters = [
+                                       'drive' => $data->drive,
+                                       'slot' => $data->slot
+                               ];
+                               $query = '?' . http_build_query($parameters);
+                               $result = $this->getModule('api')->set(
+                                       [
+                                               'devices',
+                                               $this->getDeviceName(),
+                                               'load',
+                                               $query
+                                       ],
+                                       [],
+                                       $host
+                               );
+                       } else {
+                               $result = new StdClass;
+                               $result->error = DeviceError::ERROR_DEVICE_AUTOCHANGER_DRIVE_DOES_NOT_EXIST;
+                               $result->output = Prado::localize('There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration.');
+                       }
                }
                if ($result->error === 0) {
                        $this->getCallbackClient()->callClientFunction(
@@ -592,17 +595,23 @@ class StorageView extends BaculumWebPage {
        }
 
        public function loadedDriveWithoutMount($sender, $param) {
+               if (!($host = $this->getSDAPIHost())) {
+                       return;
+               }
                $out_id = $param->getCallbackParameter();
                $parameters = [
                        'out_id' => $out_id
                ];
                $query = '?' . http_build_query($parameters);
-               $result = $this->getModule('api')->get([
-                       'devices',
-                       $this->getDeviceName(),
-                       'load',
-                       $query
-               ]);
+               $result = $this->getModule('api')->get(
+                       [
+                               'devices',
+                               $this->getDeviceName(),
+                               'load',
+                               $query
+                       ],
+                       $host
+               );
                $this->loadedDrive(
                        'oSlots.refresh_drive_without_mount_loading',
                        $out_id,
@@ -749,31 +758,44 @@ class StorageView extends BaculumWebPage {
        }
 
        private function transferSlots($slotsrc, $slotdest) {
+               if (!($host = $this->getSDAPIHost())) {
+                       return;
+               }
                $parameters = [
                        'slotsrc' => $slotsrc,
                        'slotdest' => $slotdest
                ];
                $query = '?' . http_build_query($parameters);
-               $result = $this->getModule('api')->set([
-                       'devices',
-                       $this->getDeviceName(),
-                       'transfer',
-                       $query
-               ]);
+               $result = $this->getModule('api')->set(
+                       [
+                               'devices',
+                               $this->getDeviceName(),
+                               'transfer',
+                               $query
+                       ],
+                       [],
+                       $host
+               );
                return $result;
        }
 
        private function getTransferOutput($out_id) {
+               if (!($host = $this->getSDAPIHost())) {
+                       return;
+               }
                $parameters = [
                        'out_id' => $out_id
                ];
                $query = '?' . http_build_query($parameters);
-               $result = $this->getModule('api')->get([
-                       'devices',
-                       $this->getDeviceName(),
-                       'transfer',
-                       $query
-               ]);
+               $result = $this->getModule('api')->get(
+                       [
+                               'devices',
+                               $this->getDeviceName(),
+                               'transfer',
+                               $query
+                       ],
+                       $host
+               );
                return $result;
        }
 
@@ -984,5 +1006,97 @@ class StorageView extends BaculumWebPage {
                        [false]
                );
        }
+
+       /**
+        * Set storage storageid.
+        *
+        * @return none;
+        */
+       public function setStorageId($storageid) {
+               $storageid = intval($storageid);
+               $this->setViewState(self::STORAGEID, $storageid, 0);
+       }
+
+       /**
+        * Get storage storageid.
+        *
+        * @return integer storageid
+        */
+       public function getStorageId() {
+               return $this->getViewState(self::STORAGEID, 0);
+       }
+
+       /**
+        * Set storage name.
+        *
+        * @return none;
+        */
+       public function setStorageName($storage_name) {
+               $this->setViewState(self::STORAGE_NAME, $storage_name);
+       }
+
+       /**
+        * Get storage name.
+        *
+        * @return string storage name
+        */
+       public function getStorageName() {
+               return $this->getViewState(self::STORAGE_NAME);
+       }
+
+       /**
+        * Set device name.
+        *
+        * @return none;
+        */
+       public function setDeviceName($device_name) {
+               $this->setViewState(self::DEVICE_NAME, $device_name);
+       }
+
+       /**
+        * Get device name.
+        *
+        * @return string device name
+        */
+       public function getDeviceName() {
+               return $this->getViewState(self::DEVICE_NAME);
+       }
+
+       /**
+        * Check if storage is autochanger
+        *
+        * @return bool true if autochanger, otherwise false
+        */
+       public function getIsAutochanger() {
+               return $this->getViewState(self::IS_AUTOCHANGER, false);
+       }
+
+       /**
+        * Set autochanger value for storage
+        *
+        * @return none;
+        */
+       public function setIsAutochanger($is_autochanger) {
+               settype($is_autochanger, 'bool');
+               $this->setViewState(self::IS_AUTOCHANGER, $is_autochanger);
+       }
+
+       /**
+        * Set storage address.
+        *
+        * @return none;
+        */
+       public function setStorageAddress($address) {
+               $this->setViewState(self::STORAGE_ADDRESS, $address);
+       }
+
+       /**
+        * Get storage address.
+        *
+        * @return string address
+        */
+       public function getStorageAddress() {
+               return $this->getViewState(self::STORAGE_ADDRESS);
+       }
 }
 ?>
diff --git a/gui/baculum/protected/Web/Portlets/BaculaConfigResourceList.php b/gui/baculum/protected/Web/Portlets/BaculaConfigResourceList.php
new file mode 100644 (file)
index 0000000..28876cf
--- /dev/null
@@ -0,0 +1,232 @@
+<?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.ActiveControls.TActiveLabel');
+Prado::using('System.Web.UI.ActiveControls.TActiveRepeater');
+Prado::using('Application.Web.Portlets.BaculaConfigResources');
+Prado::using('Application.Web.Portlets.Portlets');
+
+/**
+ * Bacula config resource list control.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Control
+ * @package Baculum Web
+ */
+class BaculaConfigResourceList extends Portlets {
+
+       const HOST = 'Host';
+       const COMPONENT_TYPE = 'ComponentType';
+       const COMPONENT_NAME = 'ComponentName';
+       const RESOURCE_TYPE = 'ResourceType';
+       const RESOURCE_LIST = 'ResourceList';
+
+       public function onPreRender($param) {
+               parent::onPreRender($param);
+               if ($this->getPage()->IsCallBack || $this->getPage()->IsPostBack) {
+                       return;
+               }
+               $this->prepareTable();
+       }
+
+       private function prepareTable() {
+               $res_list = $this->getResourceList();
+               $this->ResourceListHeaderRepeater->DataSource = $res_list;
+               $this->ResourceListHeaderRepeater->dataBind();
+               $this->ResourceListFooterRepeater->DataSource = $res_list;
+               $this->ResourceListFooterRepeater->dataBind();
+               $this->ResourceListColumnsRepeater->DataSource = $res_list;
+               $this->ResourceListColumnsRepeater->dataBind();
+       }
+
+       public function showError($show) {
+               $cbc = $this->getPage()->getCallbackClient();
+               if ($show) {
+                       $cbc->hide($this->ClientID . '_container');
+                       $cbc->show($this->ClientID . '_error_msg');
+               } else {
+                       $cbc->hide($this->ClientID . '_error_msg');
+                       $cbc->show($this->ClientID . '_container');
+               }
+       }
+
+       public function loadResourceListTable() {
+               $this->showError(false);
+               $component_type = $this->getComponentType();
+               $resource_type = $this->getResourceType();
+               $this->ResourceTypeLink->Text = $resource_type;
+               $config = $this->getModule('api')->get(
+                       [
+                               'config',
+                               $component_type,
+                               $resource_type
+                       ],
+                       $this->getHost()
+               );
+               if ($config->error === 0) {
+                       $res_list = $this->getResourceList();
+                       $directives = [];
+                       for ($i = 0; $i < count($config->output); $i++) {
+                               $data = [];
+                               for ($j = 0; $j < count($res_list); $j++) {
+                                       if (property_exists($config->output[$i]->{$resource_type}, $res_list[$j]['name'])) {
+                                               $data[$res_list[$j]['name']] = $config->output[$i]->{$resource_type}->{$res_list[$j]['name']};
+                                       }
+                               }
+                               $directives[] = $data;
+                       }
+                       $this->getPage()->getCallbackClient()->callClientFunction(
+                               'oBaculaConfigResourceList' . $this->ClientID . '.init',
+                               [$directives]
+                       );
+               }
+               $this->ResourceConfig->unloadDirectives();
+       }
+
+       public function loadResourceWindow($sender, $param) {
+               list($cmd, $name) = $param->getCallbackParameter();
+               if (!empty($name)) {
+                       // edit existing resource
+                       $this->ResourceConfig->setResourceName($name);
+                       $this->ResourceConfig->setLoadValues(true);
+               } else {
+                       // add new resource
+                       $this->ResourceConfig->setLoadValues(false);
+                       $this->getPage()->getCallbackClient()->callClientFunction(
+                               'oBaculaConfigSection.show_sections',
+                               [true]
+                       );
+               }
+               $host = $this->getHost();
+               $component_type = $this->getComponentType();
+               $component_name = $this->getComponentName();
+               $resource_type = $this->getResourceType();
+               $this->ResourceConfig->setHost($host);
+               $this->ResourceConfig->setComponentType($component_type);
+               $this->ResourceConfig->setComponentName($component_name);
+               $this->ResourceConfig->setResourceType($resource_type);
+               $this->ResourceConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+       }
+
+       public function unloadResourceWindow($sender, $param) {
+               $this->ResourceConfig->unloadDirectives();
+       }
+
+       public function removeResource($sender, $param) {
+               $host = $this->getHost();
+               $component_type = $this->getComponentType();
+               $resource_type = $this->getResourceType();
+               $resource_name = $param->getCallbackParameter();
+               $result = $this->getModule('api')->get(
+                       [
+                               'config',
+                               $component_type
+                       ],
+                       $host
+               );
+               $config = [];
+               if (is_object($result) && $result->error === 0 && is_array($result->output)) {
+                       $config = $result->output;
+               }
+               $deps = $this->getModule('data_deps')->checkDependencies(
+                       $component_type,
+                       $resource_type,
+                       $resource_name,
+                       $config
+               );
+               if (count($deps) === 0) {
+                       // NO DEPENDENCY. Ready to remove.
+                       BaculaConfigResources::removeResourceFromConfig(
+                               $config,
+                               $resource_type,
+                               $resource_name
+                       );
+                       $result = $this->getModule('api')->set(
+                               ['config', $component_type],
+                               ['config' => json_encode($config)],
+                               $host,
+                               false
+                       );
+                       if ($result->error !== 0) {
+                               $this->showRemovedResourceError($result->output);
+                       } else {
+                               $this->loadResourceListTable($sender, $param);
+                       }
+               } else {
+                       // DEPENDENCIES EXIST. List them on the interface.
+                       $error_message = BaculaConfigResources::prepareDependenciesError(
+                               $deps,
+                               $resource_type,
+                               $resource_name
+                       );
+                       $this->showRemovedResourceError($error_message);
+               }
+       }
+
+       private function showRemovedResourceError($error_message) {
+               $this->RemoveResourceError->Text = $error_message;
+               $err_win_id = 'resource_error_window' . $this->ClientID;
+               $this->getPage()->getCallbackClient()->show($err_win_id);
+       }
+
+       public function getHost() {
+               return $this->getViewState(self::HOST);
+       }
+
+       public function setHost($host) {
+               $this->setViewState(self::HOST, $host);
+       }
+
+       public function getComponentType() {
+               return $this->getViewState(self::COMPONENT_TYPE);
+       }
+
+       public function setComponentType($type) {
+               $this->setViewState(self::COMPONENT_TYPE, $type);
+       }
+
+       public function getComponentName() {
+               return $this->getViewState(self::COMPONENT_NAME);
+       }
+
+       public function setComponentName($name) {
+               $this->setViewState(self::COMPONENT_NAME, $name);
+       }
+
+       public function getResourceType() {
+               return $this->getViewState(self::RESOURCE_TYPE);
+       }
+
+       public function setResourceType($type) {
+               $this->setViewState(self::RESOURCE_TYPE, $type);
+       }
+
+       public function getResourceList() {
+               return $this->getViewState(self::RESOURCE_LIST, []);
+       }
+
+       public function setResourceList($list) {
+               $this->setViewState(self::RESOURCE_LIST, $list);
+       }
+}
+?>
+
diff --git a/gui/baculum/protected/Web/Portlets/BaculaConfigResourceList.tpl b/gui/baculum/protected/Web/Portlets/BaculaConfigResourceList.tpl
new file mode 100644 (file)
index 0000000..86c1957
--- /dev/null
@@ -0,0 +1,196 @@
+<span id="<%=$this->ClientID%>_error_msg" class="w3-text-red" style="display: none">
+       <%[ There was a problem with loading the resource configuration. Please check if selected API host is working and if it provides access to the resource configuration. ]%>
+</span>
+<div id="<%=$this->ClientID%>_container">
+       <div class="w3-container">
+               <a href="javascript:void(0)" class="w3-button w3-margin-bottom w3-green" onclick="oBaculaConfigResourceWindow<%=$this->ClientID%>.load_resource_window();"><i class="fa fa-plus"></i> &nbsp;<%[ Add ]%> <com:TActiveLabel ID="ResourceTypeLink" /></a>
+       </div>
+       <table id="<%=$this->ClientID%>_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+               <thead>
+                       <tr>
+                               <th></th>
+                               <com:Application.Common.Portlets.BSimpleRepeater ID="ResourceListHeaderRepeater">
+                                       <prop:ItemTemplate>
+                                               <th class="center"><%=$this->Data['label']%></th>
+                                       </prop:ItemTemplate>
+                               </com:Application.Common.Portlets.BSimpleRepeater>
+                               <th><%[ Actions ]%></th>
+                       </tr>
+               </thead>
+               <tbody id="<%=$this->ClientID%>_list_body"></tbody>
+               <tfoot>
+                       <tr>
+                               <th></th>
+                               <com:Application.Common.Portlets.BSimpleRepeater ID="ResourceListFooterRepeater">
+                                       <prop:ItemTemplate>
+                                               <th class="center"><%=$this->Data['label']%></th>
+                                       </prop:ItemTemplate>
+                               </com:Application.Common.Portlets.BSimpleRepeater>
+                               <th><%[ Actions ]%></th>
+                       </tr>
+               </tfoot>
+       </table>
+</div>
+<script>
+var oBaculaConfigResourceList<%=$this->ClientID%> = {
+       ids: {
+               list: '<%=$this->ClientID%>_list',
+               list_body: '<%=$this->ClientID%>_list_body'
+       },
+       data: [],
+       table: null,
+       init: function(data) {
+               var self = oBaculaConfigResourceList<%=$this->ClientID%>;
+               self.data = data;
+               if (self.table) {
+                       var page = self.table.page();
+                       self.table.clear().rows.add(self.data).draw();
+                       self.table.page(page).draw(false);
+               } else {
+                       self.set_table();
+               }
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.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>'
+                                               }
+                                               <com:Application.Common.Portlets.BSimpleRepeater ID="ResourceListColumnsRepeater">
+                                                       <prop:ItemTemplate>
+                                                               ,{data: '<%=$this->Data['name']%>'}
+                                                       </prop:ItemTemplate>
+                                               </com:Application.Common.Portlets.BSimpleRepeater>
+                                               ,{
+                                                       data: 'Name',
+                                                       render: function (data, type, row) {
+                                                               var span = document.createElement('SPAN');
+                                                               span.className = 'w3-right';
+
+                                                               var edit_btn = document.createElement('BUTTON');
+                                                               edit_btn.className = 'w3-button w3-green w3-margin-right';
+                                                               edit_btn.type = 'button';
+                                                               var i = document.createElement('I');
+                                                               i.className = 'fa fa-list-ul';
+                                                               var label = document.createTextNode(' <%[ Edit ]%>');
+                                                               edit_btn.appendChild(i);
+                                                               edit_btn.innerHTML += '&nbsp';
+                                                               edit_btn.appendChild(label);
+                                                               edit_btn.setAttribute('onclick', 'oBaculaConfigResourceWindow<%=$this->ClientID%>.load_resource_window("' + data + '");');
+
+                                                               var del_btn = document.createElement('BUTTON');
+                                                               del_btn.className = 'w3-button w3-red';
+                                                               del_btn.type = 'button';
+                                                               var i = document.createElement('I');
+                                                               i.className = 'fa fa-trash-alt';
+                                                               var label = document.createTextNode(' <%[ Delete ]%>');
+                                                               del_btn.appendChild(i);
+                                                               del_btn.innerHTML += '&nbsp';
+                                                               del_btn.appendChild(label);
+                                                               del_btn.setAttribute('onclick', 'oBaculaConfigResourceWindow<%=$this->ClientID%>.remove_resource("' + data + '")');
+
+                                                               span.appendChild(edit_btn);
+                                                               span.appendChild(del_btn);
+                                                               return span.outerHTML;
+                                                       }
+                                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: 'control',
+                               orderable: false,
+                               targets: 0
+                       },
+                       {
+                               className: "dt-center",
+                               targets: [ 2 ]
+                       }],
+                       order: [1, 'asc']
+               });
+       }
+};
+//oBaculaConfigResourceList<%=$this->ClientID%>.init();
+</script>
+<com:TCallback ID="RemoveResource" OnCallback="removeResource" />
+<div id="resource_window<%=$this->ClientID%>" class="w3-modal">
+       <div class="w3-modal-content w3-animate-top w3-card-4">
+               <header class="w3-container w3-teal">
+                       <span onclick="oBaculaConfigResourceWindow<%=$this->ClientID%>.close_resource_window();" class="w3-button w3-display-topright">&times;</span>
+                       <h2 id="resource_window_title_add<%=$this->ClientID%>" style="display: none"><%[ Add ]%> <%=$this->getResourceType()%></h2>
+                       <h2 id="resource_window_title_edit<%=$this->ClientID%>" style="display: none"><%[ Edit ]%> <%=$this->getResourceType()%></h2>
+               </header>
+               <div class="w3-container w3-margin-left w3-margin-right w3-margin-top w3-text-teal">
+                       <com:Application.Web.Portlets.BaculaConfigDirectives
+                               ID="ResourceConfig"
+                               ShowRemoveButton="false"
+                               ShowCancelButton="false"
+                               ShowBottomButtons="false"
+                               SaveDirectiveActionOk="oBaculaConfigResourceWindow<%=$this->ClientID%>.close_resource_window();"
+                               OnSave="loadResourceListTable"
+                       />
+               </div>
+       </div>
+       <com:TActiveHiddenField ID="ResourceWindowType" />
+</div>
+<div id="resource_error_window<%=$this->ClientID%>" class="w3-modal">
+       <div class="w3-modal-content w3-animate-top w3-card-4">
+               <header class="w3-container w3-red">
+                       <span onclick="document.getElementById('resource_error_window<%=$this->ClientID%>').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                       <h2><%[ Error ]%></h2>
+               </header>
+               <div class="w3-container w3-margin-left w3-margin">
+                       <com:TActiveLabel ID="RemoveResourceError" CssClass="w3-text-red" />
+               </div>
+               <footer class="w3-container w3-center">
+                       <button type="button" class="w3-button w3-red w3-margin-bottom" onclick="document.getElementById('resource_error_window<%=$this->ClientID%>').style.display = 'none';"><i class="fas fa-times"></i> &nbsp;<%[ Close ]%></button>
+               </footer>
+       </div>
+</div>
+<com:TCallback ID="LoadResource" OnCallback="TemplateControl.loadResourceWindow" />
+<com:TCallback ID="UnloadResource" OnCallback="TemplateControl.unloadResourceWindow" />
+<script>
+oBaculaConfigResourceWindow<%=$this->ClientID%> = {
+       load_resource_window: function(name) {
+               var title_add = document.getElementById('resource_window_title_add<%=$this->ClientID%>');
+               var title_edit = document.getElementById('resource_window_title_edit<%=$this->ClientID%>');
+               var cb = <%=$this->LoadResource->ActiveControl->Javascript%>;
+               cb.setCallbackParameter([null, name]);
+               cb.dispatch();
+               if (name) {
+                       title_add.style.display = 'none';
+                       title_edit.style.display = 'inline-block';
+               } else {
+                       title_edit.style.display = 'none';
+                       title_add.style.display = 'inline-block';
+               }
+               document.getElementById('resource_window<%=$this->ClientID%>').style.display = 'block';
+       },
+       unload_resource_window: function() {
+               var cb = <%=$this->UnloadResource->ActiveControl->Javascript%>;
+               cb.dispatch();
+       },
+       close_resource_window: function() {
+               document.getElementById('resource_window<%=$this->ClientID%>').style.display = 'none';
+               this.unload_resource_window();
+       },
+       remove_resource: function(name) {
+               var cb = <%=$this->RemoveResource->ActiveControl->Javascript%>;
+               cb.setCallbackParameter(name);
+               cb.dispatch();
+       }
+};
+</script>
index 9059d224432192b8e82593b238e421a8dbbf8388..3eab718c04590e141fb7333f7a6722720a3bdc1d 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2021 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -124,7 +124,7 @@ class BaculaConfigResources extends ResourceListTemplate {
                );
                if (count($deps) === 0) {
                        // NO DEPENDENCY. Ready to remove.
-                       $this->removeResourceFromConfig(
+                       self::removeResourceFromConfig(
                                $config,
                                $host_params['resource_type'],
                                $host_params['resource_name']
@@ -145,11 +145,12 @@ class BaculaConfigResources extends ResourceListTemplate {
                        }
                } else {
                        // DEPENDENCIES EXIST. List them on the interface.
-                       $this->showDependenciesError(
+                       $error_message = self::prepareDependenciesError(
                                $deps,
                                $host_params['resource_type'],
                                $host_params['resource_name']
                        );
+                       $this->showRemovedResourceError($error_message);
                }
        }
 
@@ -186,13 +187,14 @@ class BaculaConfigResources extends ResourceListTemplate {
 
        /**
         * Show dependencies error message.
+        * NOTE: Method called also externally (@see BaculaConfigResourceList)
         *
         * @param array $deps list dependencies for the removing resource
         * @param string $resource_type resource type of the removing resource
         * @param string $resource_name resource name of the removing resource
-        * @return none
+        * @return string error message
         */
-       private function showDependenciesError($deps, $resource_type, $resource_name) {
+       public static function prepareDependenciesError($deps, $resource_type, $resource_name) {
                $emsg = Prado::localize('Resource %s "%s" is used in the following resources:');
                $emsg = sprintf($emsg, $resource_type, $resource_name);
                $emsg_deps = Prado::localize('Component: %s, Resource: %s "%s", Directive: %s');
@@ -209,20 +211,20 @@ class BaculaConfigResources extends ResourceListTemplate {
                $emsg_sum = Prado::localize('Please unassign resource %s "%s" from these resources and try again.');
                $emsg_sum = sprintf($emsg_sum, $resource_type, $resource_name);
                $error = array($emsg, implode('<br />', $dependencies),  $emsg_sum);
-               $error_message = implode('<br /><br />', $error);
-               $this->showRemovedResourceError($error_message);
+               return implode('<br /><br />', $error);
        }
 
        /**
         * Remove resource from config.
         * Note, passing config by reference.
+        * NOTE: Method called also externally (@see BaculaConfigResourceList)
         *
         * @param array $config entire config
         * @param string $resource_type resource type to remove
         * @param string $resource_name resource name to remove
         * @return none
         */
-       public function removeResourceFromConfig(&$config, $resource_type, $resource_name) {
+       public static function removeResourceFromConfig(&$config, $resource_type, $resource_name) {
                for ($i = 0; $i < count($config); $i++) {
                        foreach ($config[$i] as $rtype => $resource) {
                                if (!property_exists($resource, 'Name')) {
index f0d4571c278e06f735da6d233eb2565132116e2e..1e7eb7218514a0608d897448d57d5958958bf2ad 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2021 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -44,6 +44,9 @@ abstract class DirectiveControlTemplate extends TTemplateControl {
                                $command_param = $this->getPage()->PostBackEventTarget->getCommandParameter();
                        }
                }
+               if (is_array($command_param) && count($command_param) > 0) {
+                       $command_param = $command_param[0];
+               }
                return $command_param;
        }
 
index 73ee332f38bef048bd24d330ca7586d4f8035bff..2e7b5f818e697dd38034f61fc502cf0a4919256c 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2021 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -75,7 +75,7 @@ class DirectiveMessages extends DirectiveListTemplate {
                $load_values = $this->getLoadValues();
                $dests = $this->getData();
                if (key_exists('Destinations', $dests)) {
-                       $dests = $dests['Destinations'];
+                       $dests = array_filter($dests['Destinations']);
                }
                $directives = array();
                for ($i = 0; $i < count($dests); $i++) {
index fbb39cff89c9b09c3ec6c517f52c58d77f149311..0ae54fc23cd8f594b9dfa9358c6292f147ee2a30 100644 (file)
@@ -1,4 +1,4 @@
-<button type="button" class="w3-button w3-green w3-margin" onmousedown="openElementOnCursor(event, '<%=$this->MessagesMenu->ClientID%>_new_messages', 0, 20);"><i class="fa fa-plus"></i> &nbsp;<%[ Add ]%></button>
+<button type="button" class="w3-button w3-green w3-margin" onclick="openElementOnCursor(event, '<%=$this->MessagesMenu->ClientID%>_new_messages', 0, 20);"><i class="fa fa-plus"></i> &nbsp;<%[ Add ]%></button>
 <p class="bold"><%[ Tip: checking 'All' message type causes, that rest checked message types are saved with negation ex. Catalog = All, !Debug, !Saved, !Skipped ]%></p>
 <com:Application.Web.Portlets.NewMessagesMenu ID="MessagesMenu" />
 <com:TActiveRepeater ID="RepeaterMessages" OnItemCreated="createDirectiveListElement" OnItemDataBound="loadMessageTypes">
index 9673c8e1964bf5cad1163b32918aad91aad42c9b..4a43491beafec205de2b219e7b254d4996f991f0 100644 (file)
@@ -2,6 +2,7 @@
        <!-- webGUI endpoints -->
        <url ServiceParameter="Dashboard" pattern="web/" />
        <url ServiceParameter="LoginPage" pattern="web/login/" />
+       <url ServiceParameter="SelectAPIHost" pattern="web/api-host/" />
        <url ServiceParameter="JobHistoryList" pattern="web/job/history/" />
        <url ServiceParameter="JobHistoryView" pattern="web/job/history/{jobid}/" parameters.jobid="\d+" />
        <url ServiceParameter="JobList" pattern="web/job/" />