]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Implement API version 2
authorMarcin Haba <marcin.haba@bacula.pl>
Sat, 27 Feb 2021 21:55:53 +0000 (22:55 +0100)
committerEric Bollengier <eric@baculasystems.com>
Tue, 2 Mar 2021 10:52:19 +0000 (11:52 +0100)
APIv2 changes:
 - Send request body parameters to the API in JSON format instead of POST form parameters.
 - Drop using the create[] and update[] surrounds in the POST and PUT request body parameters.
 - Move the /api/v1/status/{director|storage|client}/ endpoints to:
    = /api/v2/clients/{clientid}/status,
    = /api/v2/storages/{storageid}/status,
    = /api/v2/directors/{director_name}/status.
 - Stop using OAuth2 'status' scope.
 - API version 1 is still possible to use and it is preserved. Nothing changes here.
 - New and modern API admin panel.

90 files changed:
gui/baculum/LICENSE
gui/baculum/Makefile
gui/baculum/protected/API/Class/APIInterfaces.php [new file with mode: 0644]
gui/baculum/protected/API/Class/APIServer.php [new file with mode: 0644]
gui/baculum/protected/API/Class/APIServerV1.php [new file with mode: 0644]
gui/baculum/protected/API/Class/APIServerV2.php [new file with mode: 0644]
gui/baculum/protected/API/Class/BaculumAPIPage.php
gui/baculum/protected/API/Class/BaculumAPIServer.php
gui/baculum/protected/API/Class/StatusClient.php
gui/baculum/protected/API/JavaScript/misc.js [deleted file]
gui/baculum/protected/API/Lang/en/messages.mo
gui/baculum/protected/API/Lang/en/messages.po
gui/baculum/protected/API/Lang/pl/messages.mo
gui/baculum/protected/API/Lang/pl/messages.po
gui/baculum/protected/API/Lang/pt/messages.mo
gui/baculum/protected/API/Lang/pt/messages.po
gui/baculum/protected/API/Lang/ru/messages.mo
gui/baculum/protected/API/Lang/ru/messages.po
gui/baculum/protected/API/Layouts/Main.tpl
gui/baculum/protected/API/Layouts/Wizard.tpl
gui/baculum/protected/API/Pages/API/ClientStatus.php
gui/baculum/protected/API/Pages/API/DirectorStatus.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/API/StorageStatus.php
gui/baculum/protected/API/Pages/API/config.xml
gui/baculum/protected/API/Pages/API/endpoints.xml
gui/baculum/protected/API/Pages/Panel/APIBasicUsers.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/Panel/APIBasicUsers.tpl [new file with mode: 0644]
gui/baculum/protected/API/Pages/Panel/APIHome.page
gui/baculum/protected/API/Pages/Panel/APIHome.php
gui/baculum/protected/API/Pages/Panel/APIInstallWizard.page
gui/baculum/protected/API/Pages/Panel/APIInstallWizard.php
gui/baculum/protected/API/Pages/Panel/APIOAuth2Clients.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/Panel/APIOAuth2Clients.tpl [new file with mode: 0644]
gui/baculum/protected/API/Pages/Panel/APISettings.page [new file with mode: 0644]
gui/baculum/protected/API/Pages/Panel/APISettings.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/Panel/endpoints.xml
gui/baculum/protected/API/Portlets/APISideBar.php [new file with mode: 0644]
gui/baculum/protected/API/Portlets/APISideBar.tpl [new file with mode: 0644]
gui/baculum/protected/API/openapi_baculum.json
gui/baculum/protected/Common/Class/AuthBasic.php
gui/baculum/protected/Common/Class/AuthOAuth2.php
gui/baculum/protected/Common/Class/BClientScript.php
gui/baculum/protected/Common/Class/BaculumUrlMapping.php
gui/baculum/protected/Common/JavaScript/buttons.colVis.js [moved from gui/baculum/protected/Web/JavaScript/buttons.colVis.js with 100% similarity]
gui/baculum/protected/Common/JavaScript/buttons.html5.js [moved from gui/baculum/protected/Web/JavaScript/buttons.html5.js with 100% similarity]
gui/baculum/protected/Common/JavaScript/dataTables.buttons.js [moved from gui/baculum/protected/Web/JavaScript/dataTables.buttons.js with 100% similarity]
gui/baculum/protected/Common/JavaScript/dataTables.responsive.js [moved from gui/baculum/protected/Web/JavaScript/dataTables.responsive.js with 100% similarity]
gui/baculum/protected/Common/JavaScript/dataTables.select.js [moved from gui/baculum/protected/Web/JavaScript/dataTables.select.js with 100% similarity]
gui/baculum/protected/Common/JavaScript/datatables.js [moved from gui/baculum/protected/Web/JavaScript/datatables.js with 100% similarity]
gui/baculum/protected/Common/JavaScript/fontawesome.min.js [moved from gui/baculum/protected/Web/JavaScript/fontawesome.min.js with 100% similarity]
gui/baculum/protected/Common/JavaScript/misc.js [new file with mode: 0644]
gui/baculum/protected/Common/JavaScript/responsive.jqueryui.js [moved from gui/baculum/protected/Web/JavaScript/responsive.jqueryui.js with 100% similarity]
gui/baculum/protected/Common/Portlets/NewAuthClient.php
gui/baculum/protected/Common/Portlets/NewAuthClient.tpl
gui/baculum/protected/Web/Class/BaculumAPIClient.php
gui/baculum/protected/Web/JavaScript/misc.js
gui/baculum/protected/Web/Layouts/Main.tpl
gui/baculum/protected/Web/Layouts/MessageBox.tpl
gui/baculum/protected/Web/Layouts/Simple.tpl
gui/baculum/protected/Web/Layouts/Wizard.tpl
gui/baculum/protected/Web/Pages/ClientView.php
gui/baculum/protected/Web/Pages/StorageView.php
gui/baculum/themes/Baculum-v1/ajax-loader.gif [deleted file]
gui/baculum/themes/Baculum-v1/ajax-loader.orig.gif [deleted file]
gui/baculum/themes/Baculum-v1/background.png [deleted file]
gui/baculum/themes/Baculum-v1/bls_bottom.png [deleted file]
gui/baculum/themes/Baculum-v1/bls_top.png [deleted file]
gui/baculum/themes/Baculum-v1/close.png [deleted file]
gui/baculum/themes/Baculum-v1/css/style.css [deleted file]
gui/baculum/themes/Baculum-v1/css/wizard.css [deleted file]
gui/baculum/themes/Baculum-v1/favicon.ico [deleted file]
gui/baculum/themes/Baculum-v1/icon_close.png [deleted file]
gui/baculum/themes/Baculum-v1/icon_err.png [deleted file]
gui/baculum/themes/Baculum-v1/icon_ok.png [deleted file]
gui/baculum/themes/Baculum-v1/icon_refresh.png [deleted file]
gui/baculum/themes/Baculum-v1/panel-border-bg.png [deleted file]
gui/baculum/themes/Baculum-v1/step-active.png [deleted file]
gui/baculum/themes/Baculum-v1/step-bar.png [deleted file]
gui/baculum/themes/Baculum-v1/step-content.png [deleted file]
gui/baculum/themes/Baculum-v1/step-first-active.png [deleted file]
gui/baculum/themes/Baculum-v1/step-first-next-active.png [deleted file]
gui/baculum/themes/Baculum-v1/step-first.png [deleted file]
gui/baculum/themes/Baculum-v1/step-head.png [deleted file]
gui/baculum/themes/Baculum-v1/step-last-1.png [deleted file]
gui/baculum/themes/Baculum-v1/step-last-active-1.png [deleted file]
gui/baculum/themes/Baculum-v1/step-prev-active.png [deleted file]
gui/baculum/themes/Baculum-v1/step.png [deleted file]
gui/baculum/themes/Baculum-v1/wizard-bottom-1.png [deleted file]
gui/baculum/themes/Baculum-v1/wizard-header-1.png [deleted file]
gui/baculum/themes/Baculum-v2/css/baculum.css

index 9e83459afc4a398ef5687e7cb36ff5a3237ad627..83e320b04eda86302090f3e8bd738dac334bf363 100644 (file)
@@ -815,13 +815,13 @@ THE SOFTWARE.
 Baculum uses DataTables library for tables.
 
 Files:
-  protected/Web/JavaScript/datatables.js
-  protected/Web/JavaScript/dataTables.responsive.js
-  protected/Web/JavaScript/dataTables.buttons.js
-  protected/Web/JavaScript/dataTables.select.js
-  protected/Web/JavaScript/responsive.jqueryui.js
-  protected/Web/JavaScript/buttons.colVis.js
-  protected/Web/JavaScript/buttons.html5.js
+  protected/Common/JavaScript/datatables.js
+  protected/Common/JavaScript/dataTables.responsive.js
+  protected/Common/JavaScript/dataTables.buttons.js
+  protected/Common/JavaScript/dataTables.select.js
+  protected/Common/JavaScript/responsive.jqueryui.js
+  protected/Common/JavaScript/buttons.colVis.js
+  protected/Common/JavaScript/buttons.html5.js
   themes/Baculum-v2/css/datatables.css
   themes/Baculum-v2/css/responsive.dataTables.css
   themes/Baculum-v2/css/buttons.dataTables.css
@@ -852,7 +852,7 @@ The Buttons DataTables plugin uses FileSaver.js for saving files on
 the client-side.
 
 Files:
-  protected/Web/JavaScript/buttons.html5.js
+  protected/Common/JavaScript/buttons.html5.js
 
 FileSaver.js (1.3.3) - MIT license
 Copyright © 2016 Eli Grey - http://eligrey.com
@@ -862,7 +862,7 @@ Copyright © 2016 Eli Grey - http://eligrey.com
 Baculum uses Font Awesome Free for icons.
 
 Files:
-  protected/Web/JavaScript/fontawesome.min.js
+  protected/Common/JavaScript/fontawesome.min.js
   themes/Baculum-v2/css/fontawesome-all.min.css
   themes/Baculum-v2/webfonts/fa-brands-400.eot
   themes/Baculum-v2/webfonts/fa-brands-400.svg
index c994ba273be44e06cb743e3d5de60b444a16890d..0ecf60ec86bb5343c772fd78d388dce86078cd6c 100644 (file)
@@ -48,14 +48,17 @@ webdatadirsrc = $(datadir)/$(webdir)/Class \
 
 apidatadirsrc = $(datadir)/$(apidir)/Class \
        $(datadir)/$(apidir)/Config \
-       $(datadir)/$(apidir)/JavaScript \
        $(datadir)/$(apidir)/Layouts \
        $(datadir)/$(apidir)/Logs \
-       $(datadir)/$(apidir)/Pages
+       $(datadir)/$(apidir)/Pages \
+       $(datadir)/$(apidir)/Portlets
 
 commondatadirsrc = $(datadir)/$(commondir)/Class \
+       $(datadir)/$(commondir)/JavaScript \
+       $(datadir)/$(commondir)/Pages \
        $(datadir)/$(commondir)/Portlets
 
+
 datafilesrc = $(datadir)/application.xml
 
 webdatafilesrc = $(datadir)/$(webdir)/endpoints.xml \
diff --git a/gui/baculum/protected/API/Class/APIInterfaces.php b/gui/baculum/protected/API/Class/APIInterfaces.php
new file mode 100644 (file)
index 0000000..8c58130
--- /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.
+ */
+
+/**
+ * API interfaces.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Interfaces
+ * @package Baculum API
+ */
+
+/**
+ * Defines methods to work with API Server.
+ */
+interface IAPIServer {
+
+       public function get();
+
+       public function put();
+
+       public function post();
+
+       public function delete();
+}
+?>
diff --git a/gui/baculum/protected/API/Class/APIServer.php b/gui/baculum/protected/API/Class/APIServer.php
new file mode 100644 (file)
index 0000000..e3e1508
--- /dev/null
@@ -0,0 +1,76 @@
+<?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.
+ */
+
+/**
+ * API Server layer.
+ * Introduces main method inherited by particular API server versions.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum API
+ */
+class APIServer extends APIModule {
+
+       /**
+        * Default API version if there was not possible to determine version.
+        */
+       const DEFAULT_VERSION = 2;
+
+       const VERSION_PATTERN = '!/api/v(?P<version>\d+)/!';
+
+       /**
+        * Stores API server instance.
+        */
+       protected $server = null;
+
+       /**
+        * Set API server instance.
+        *
+        * @param BaculumAPIServer $obj server object
+        * @return none
+        */
+       public function setServerObj($obj) {
+               $this->server = $obj;
+       }
+
+       /**
+        * Get API server instance.
+        * @return BaculumAPIServer|null API server object
+        */
+       public function getServerObj() {
+               return $this->server;
+       }
+
+       /**
+        * Get requested API version from URL path.
+        *
+        * @return integer requested API version number
+        */
+       public static function getVersion() {
+               $version = self::DEFAULT_VERSION;
+               $path = Prado::getApplication()->Request->getPathInfo();
+               if (preg_match(self::VERSION_PATTERN, $path, $match) === 1) {
+                       $version = intval($match['version']);
+               }
+               return $version;
+       }
+}
diff --git a/gui/baculum/protected/API/Class/APIServerV1.php b/gui/baculum/protected/API/Class/APIServerV1.php
new file mode 100644 (file)
index 0000000..35be0af
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.API.Class.APIServer');
+Prado::using('Application.API.Class.APIInterfaces');
+
+/**
+ * API Server version 1.
+ * This version receives parameters as GET and POST parameters.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum API
+ */
+class APIServerV1 extends APIServer implements IAPIServer {
+
+       /**
+        * Support for API GET method request.
+        *
+        * @return none;
+        */
+       public function get() {
+               $this->getServerObj()->get();
+       }
+
+       /**
+        * Support for API PUT method request.
+        *
+        * @return none
+        */
+       public function put() {
+               $id = $this->Request->contains('id') ? intval($this->Request['id']) : 0;
+               $params = new StdClass;
+
+               /**
+                * Check if it is possible to read PUT method data.
+                * Note that some clients sends data in PUT request as PHP input stream which
+                * is not possible to read by $_REQUEST data. From this reason, when is
+                * not possible to ready by superglobal $_REQUEST variable, then is try to
+                * read PUT data by PHP input stream.
+                */
+               if ($this->Request->contains('update') && is_array($this->Request['update']) && count($this->Request['update']) > 0) {
+                       // $_REQUEST available to read
+                       $params = (object)$this->Request['update'];
+               } else {
+                       // no possibility to read data from $_REQUEST. Try to load from input stream.
+                       $inputstr = file_get_contents("php://input");
+
+                       /**
+                        * Read using chunks for case large updates (over 1000 values).
+                        * Otherwise max_input_vars limitation in php.ini can be reached (usually
+                        * set to 1000 variables)
+                        * @see http://php.net/manual/en/info.configuration.php#ini.max-input-vars
+                        */
+                       $chunks = explode('&', $inputstr);
+
+                       $response_data = array();
+                       for($i = 0; $i<count($chunks); $i++) {
+                               // if chunks would not be used, then here occurs reach max_input_vars limit
+                               parse_str($chunks[$i], $response_el);
+                               if (is_array($response_el) && array_key_exists('update', $response_el) && is_array($response_el['update'])) {
+                                       $key = key($response_el['update']);
+                                       $response_data['update'][$key] = $response_el['update'][$key];
+                               }
+                       }
+                       if (is_array($response_data) && array_key_exists('update', $response_data)) {
+                               $params = (object)$response_data['update'];
+                       }
+               }
+               $this->getServerObj()->set($id, $params);
+       }
+
+       /**
+        * Support for API POST method request.
+        *
+        * @return none
+        */
+       public function post() {
+               $params = new StdClass;
+               if ($this->Request->contains('create') && is_array($this->Request['create']) && count($this->Request['create']) > 0) {
+                       $params = (object)$this->Request['create'];
+               }
+               $this->getServerObj()->create($params);
+       }
+
+       /**
+        * Support for API DELETE method request.
+        *
+        * @return none
+        */
+       public function delete() {
+               $id = null;
+               if ($this->Request->contains('id')) {
+                       $id = $this->Request['id'];
+               }
+               $this->getServerObj()->remove($id);
+       }
+}
+?>
diff --git a/gui/baculum/protected/API/Class/APIServerV2.php b/gui/baculum/protected/API/Class/APIServerV2.php
new file mode 100644 (file)
index 0000000..ba9b86d
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.API.Class.APIServer');
+Prado::using('Application.API.Class.APIInterfaces');
+
+/**
+ * API Server version 2.
+ * This version receives parameters as GET and POST parameters.
+ * Main difference comparing to version 1 is that POST params are sent as
+ * a JSON string in POST requests body.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum API
+ */
+
+class APIServerV2 extends APIServer implements IAPIServer {
+
+       /**
+        * Support for API GET method request.
+        *
+        * @return none;
+        */
+       public function get() {
+               $this->getServerObj()->get();
+       }
+
+       /**
+        * Support for API PUT method request.
+        *
+        * @return none
+        */
+       public function put() {
+               $id = $this->Request->contains('id') ? intval($this->Request['id']) : 0;
+               $inputstr = file_get_contents("php://input");
+               $params = json_decode($inputstr);
+               if (is_null($params)) {
+                       $params = new StdClass;
+               }
+               $this->getServerObj()->set($id, $params);
+       }
+
+       /**
+        * Support for API POST method request.
+        *
+        * @return none
+        */
+       public function post() {
+               $inputstr = file_get_contents("php://input");
+               $params = json_decode($inputstr);
+               if (is_null($params)) {
+                       $params = new StdClass;
+               }
+               $this->getServerObj()->create($params);
+       }
+
+       /**
+        * Support for API DELETE method request.
+        *
+        * @return none
+        */
+       public function delete() {
+               $id = null;
+               if ($this->Request->contains('id')) {
+                       $id = $this->Request['id'];
+               }
+               $this->getServerObj()->remove($id);
+       }
+}
+?>
index 32b0b401dd04c1a117ae7bb80ce938e77f75de95..432e319db435cfc1e0ddbf134b138c651675a7fe 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
@@ -38,7 +38,14 @@ class BaculumAPIPage extends BaculumPage {
        public function onPreInit($param) {
                parent::onPreInit($param);
                $config = $this->getModule('api_config')->getConfig('api');
-               $lang = array_key_exists('lang', $config) ? $config['lang'] : APIConfig::DEF_LANG;
+               if (count($config) === 0) {
+                       if ($this->Service->getRequestedPagePath() != 'APIInstallWizard') {
+                               $this->goToPage('APIInstallWizard');
+                       }
+                       // without config there is no way to use API panel
+                       return;
+               }
+               $lang = key_exists('lang', $config) ? $config['lang'] : APIConfig::DEF_LANG;
                $this->Application->getGlobalization()->Culture = $lang;
        }
 }
index 0caf5483073b73b885865b0db7e305b9a805349f..4713b6b25c672cacbfc681789356a5597858de9f 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
@@ -29,6 +29,7 @@ Prado::using('Application.API.Class.BAPIException');
 Prado::using('Application.API.Class.APIDbModule');
 Prado::using('Application.API.Class.Bconsole');
 Prado::using('Application.API.Class.OAuth2.TokenRecord');
+Prado::using('Application.API.Class.APIServer');
 
 /**
  * Abstract module from which inherits each of API module.
@@ -43,7 +44,7 @@ abstract class BaculumAPIServer extends TPage {
        /**
         * API server version (used in HTTP header)
         */
-       const API_SERVER_VERSION = 0.1;
+       const API_SERVER_VERSION = 0.2;
 
        /**
         * Storing output from API commands in numeric array.
@@ -174,22 +175,36 @@ abstract class BaculumAPIServer extends TPage {
                        header(OAuth2::HEADER_UNAUTHORIZED);
                        return;
                }
+               $this->runResource();
+       }
+
+       /**
+        * Run requested resource.
+        * It sets output and error values.
+        *
+        * @return none
+        */
+       private function runResource() {
+               $version = APIServer::getVersion();
+               $api = $this->getModule('api_server_v' . $version);
+               $api->setServerObj($this);
+
                try {
                        switch($_SERVER['REQUEST_METHOD']) {
                                case self::GET_METHOD: {
-                                       $this->get();
+                                       $api->get();
                                        break;
                                }
                                case self::POST_METHOD: {
-                                       $this->post();
+                                       $api->post();
                                        break;
                                }
                                case self::PUT_METHOD: {
-                                       $this->put();
+                                       $api->put();
                                        break;
                                }
                                case self::DELETE_METHOD: {
-                                       $this->delete();
+                                       $api->delete();
                                        break;
                                }
                        }
@@ -208,7 +223,7 @@ abstract class BaculumAPIServer extends TPage {
                                $this->output = GenericError::MSG_ERROR_INTERNAL_ERROR . ' ' . $e->getErrorMessage();
                                $this->error = GenericError::ERROR_INTERNAL_ERROR;
                        }
-               } 
+               }
        }
 
        /**
@@ -268,89 +283,6 @@ abstract class BaculumAPIServer extends TPage {
                echo $this->getOutput();
        }
 
-       /**
-        * Changing/updating values via API.
-        *
-        * @access private
-        * @return none
-        */
-       private function put() {
-               $id = $this->Request->contains('id') ? intval($this->Request['id']) : 0;
-
-               /**
-                * Check if it is possible to read PUT method data.
-                * Note that some clients sends data in PUT request as PHP input stream which
-                * is not possible to read by $_REQUEST data. From this reason, when is
-                * not possible to ready by superglobal $_REQUEST variable, then is try to
-                * read PUT data by PHP input stream.
-                */
-               if ($this->Request->contains('update') && is_array($this->Request['update']) && count($this->Request['update']) > 0) {
-                       // $_REQUEST available to read
-                       $params = (object)$this->Request['update'];
-                       $this->set($id, $params);
-               } else {
-                       // no possibility to read data from $_REQUEST. Try to load from input stream.
-                       $inputstr = file_get_contents("php://input");
-
-                       /**
-                        * Read using chunks for case large updates (over 1000 values).
-                        * Otherwise max_input_vars limitation in php.ini can be reached (usually
-                        * set to 1000 variables)
-                        * @see http://php.net/manual/en/info.configuration.php#ini.max-input-vars
-                        */
-                       $chunks = explode('&', $inputstr);
-
-                       $response_data = array();
-                       for($i = 0; $i<count($chunks); $i++) {
-                               // if chunks would not be used, then here occurs reach max_input_vars limit
-                               parse_str($chunks[$i], $response_el);
-                               if (is_array($response_el) && array_key_exists('update', $response_el) && is_array($response_el['update'])) {
-                                       $key = key($response_el['update']);
-                                       $response_data['update'][$key] = $response_el['update'][$key];
-                               }
-                       }
-                       if (is_array($response_data) && array_key_exists('update', $response_data)) {
-                               $params = (object)$response_data['update'];
-                               $this->set($id, $params);
-                       } else {
-                               /**
-                                * This case should never occur because it means that there is
-                                * given nothing to update.
-                                */
-                               $params = new stdClass;
-                               $this->set($id, $params);
-                       }
-               }
-       }
-
-       /**
-        * Creating new elements.
-        *
-        * @access private
-        * @return none
-        */
-       private function post() {
-               $params = new stdClass;
-               if ($this->Request->contains('create') && is_array($this->Request['create']) && count($this->Request['create']) > 0) {
-                       $params = (object)$this->Request['create'];
-               }
-               $this->create($params);
-       }
-
-       /**
-        * Deleting element by element ID.
-        *
-        * @access private
-        * @return none
-        */
-       private function delete() {
-               $id = null;
-               if ($this->Request->contains('id')) {
-                       $id = $this->Request['id'];
-               }
-               $this->remove($id);
-       }
-
        /**
         * Shortcut method for getting application modules instances by
         * module name.
index ece87ecf98c79bb8cb310bff73610c1c203add91..3a48fc00be78939a3871ac4dc42d3c926849b2d6 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
@@ -55,10 +55,11 @@ class StatusClient extends ComponentStatusModule {
                );
                if ($result->exitcode === 0) {
                        $ret['output'] = $this->parseStatus($result->output, $type);
+                       $ret['error'] = $result->exitcode;
                } else {
                        $ret['output'] = $result->output;
+                       $ret['error'] = GenericError::ERROR_WRONG_EXITCODE;
                }
-               $ret['error'] = $result->exitcode;
                return $ret;
        }
 
diff --git a/gui/baculum/protected/API/JavaScript/misc.js b/gui/baculum/protected/API/JavaScript/misc.js
deleted file mode 100644 (file)
index 0b6a339..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-var get_random_string = function(allowed, len) {
-       var random_string = "";
-       for(var i = 0; i < len; i++) {
-               random_string += allowed.charAt(Math.floor(Math.random() * allowed.length));
-       }
-       return random_string;
-}
-
-var OAuth2Scopes = [
-       'console',
-       'jobs',
-       'directors',
-       'clients',
-       'storages',
-       'volumes',
-       'pools',
-       'bvfs',
-       'joblog',
-       'filesets',
-       'schedules',
-       'config',
-       'status',
-       'actions',
-       'oauth2'
-];
-var set_scopes = function(field_id) {
-       document.getElementById(field_id).value = OAuth2Scopes.join(' ');
-}
index df1528f7ce9e524fd7a19b1a7be8dc48c72dadb9..701f1bc3f7b3bde8e29ea2472d43989dd5fb5850 100644 (file)
Binary files a/gui/baculum/protected/API/Lang/en/messages.mo and b/gui/baculum/protected/API/Lang/en/messages.mo differ
index 79112edb1f69d82176da0dc26f545438ed6f3094..0ea4a2c074af0554c097ff6249d827a896062f17 100644 (file)
@@ -20,14 +20,14 @@ msgstr "Language"
 msgid "select language"
 msgstr "select language"
 
-msgid "&laquo; Previous"
-msgstr "&laquo; Previous"
+msgid "Previous"
+msgstr "Previous"
 
 msgid "Cancel"
 msgstr "Cancel"
 
-msgid "Next &raquo;"
-msgstr "Next &raquo;"
+msgid "Next"
+msgstr "Next"
 
 msgid "Step 1 - select language"
 msgstr "Step 1 - select language"
@@ -230,8 +230,8 @@ msgstr "Use sudo for bconsole requests:"
 msgid "Save"
 msgstr "Save"
 
-msgid "Step 7 - Finish"
-msgstr "Step 7 - Finish"
+msgid "Step 6 - Finish"
+msgstr "Step 6 - Finish"
 
 msgid "Use sudo for Bacula JSON tools:"
 msgstr "Use sudo for Bacula JSON tools:"
@@ -311,9 +311,6 @@ msgstr "Please do not forget to disable HTTP Basic auth in the API web server co
 msgid "Refresh token"
 msgstr "Refresh token"
 
-msgid "New HTTP Basic user"
-msgstr "New HTTP Basic user"
-
 msgid "New OAuth2 client"
 msgstr "New OAuth2 client"
 
@@ -344,18 +341,9 @@ msgstr "Problem during save to config file. Please check users config file permi
 msgid "Given user already exists in config file."
 msgstr "Given user already exists in config file."
 
-msgid "List HTTP Basic users"
-msgstr "List HTTP Basic users"
-
-msgid "List OAuth2 clients"
-msgstr "List OAuth2 clients"
-
 msgid "set all scopes"
 msgstr "set all scopes"
 
-msgid "Go to configuration wizard"
-msgstr "Go to configuration wizard"
-
 msgid "Welcome on the Baculum API default page"
 msgstr "Welcome on the Baculum API default page"
 
@@ -428,8 +416,8 @@ msgstr "Authentication to Baculum REST API"
 msgid "Authentication type:"
 msgstr "Authentication type:"
 
-msgid "Step 6 - authentication to API"
-msgstr "Step 6 - authentication to API"
+msgid "Step 5 - authentication to API"
+msgstr "Step 5 - authentication to API"
 
 msgid "Actions"
 msgstr "Actions"
@@ -509,3 +497,71 @@ msgstr "Note"
 msgid "Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file."
 msgstr "Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file."
 
+msgid "Dashboard"
+msgstr "Dashboard"
+
+msgid "Basic users"
+msgstr "Basic users"
+
+msgid "OAuth2 clients"
+msgstr "OAuth2 clients"
+
+msgid "Settings"
+msgstr "Settings"
+
+msgid "Configuration wizard"
+msgstr "Configuration wizard"
+
+msgid "Welcome"
+msgstr "Welcome"
+
+msgid "Add user"
+msgstr "Add user"
+
+msgid "Edit user"
+msgstr "Edit user"
+
+msgid "Add OAuth2 client"
+msgstr "Add OAuth2 client"
+
+msgid "Are you sure?"
+msgstr "Are you sure?"
+
+msgid "API settings"
+msgstr "API settings"
+
+msgid "General"
+msgstr "General"
+
+msgid "Catalog"
+msgstr "Catalog"
+
+msgid "Console"
+msgstr "Console"
+
+msgid "Config"
+msgstr "Config"
+
+msgid "Enabled:"
+msgstr "Enabled:"
+
+msgid "Debug:"
+msgstr "Debug:"
+
+msgid "Add client"
+msgstr "Add client"
+
+msgid "Edit client"
+msgstr "Edit client"
+
+msgid "Invalid user. User may contain a-z A-Z 0-9 characters."
+msgstr "Invalid user. User may contain a-z A-Z 0-9 characters."
+
+msgid "Show/hide"
+msgstr "Show/hide"
+
+msgid "Copy to clipboard"
+msgstr "Copy to clipboard"
+
+msgid "Logout"
+msgstr "Logout"
index bc2c65b1660a9332e491276eeb4ed77070c19fc8..462ba583545dc4659435f1c2534cb33b7dd96df7 100644 (file)
Binary files a/gui/baculum/protected/API/Lang/pl/messages.mo and b/gui/baculum/protected/API/Lang/pl/messages.mo differ
index 8e516d6978aca76275a2b1f1f055b884700f6d26..65372ade74a1afc5aa67df287ac565998e4a2768 100644 (file)
@@ -24,14 +24,14 @@ msgstr "Język"
 msgid "select language"
 msgstr "wybierz język"
 
-msgid "&laquo; Previous"
-msgstr "&laquo; Wstecz"
+msgid "Previous"
+msgstr "Wstecz"
 
 msgid "Cancel"
 msgstr "Anuluj"
 
-msgid "Next &raquo;"
-msgstr "Dalej &raquo;"
+msgid "Next"
+msgstr "Dalej"
 
 msgid "Step 1 - select language"
 msgstr "Krok 1 - wybierz język"
@@ -234,8 +234,8 @@ msgstr "Użyj sudo dla zapytań bconsole:"
 msgid "Save"
 msgstr "Zapisz"
 
-msgid "Step 7 - Finish"
-msgstr "Krok 7 - Koniec"
+msgid "Step 6 - Finish"
+msgstr "Krok 6 - Koniec"
 
 msgid "Use sudo for Bacula JSON tools:"
 msgstr "Użyj sudo dla narzędzi JSON Bacula."
@@ -315,9 +315,6 @@ msgstr "Proszę nie zapomnieć wyłączyć autentykację HTTP Basic w pliku konf
 msgid "Refresh token"
 msgstr "Odśwież żeton"
 
-msgid "New HTTP Basic user"
-msgstr "Nowy użytkownik HTTP Basic"
-
 msgid "New OAuth2 client"
 msgstr "Nowy klient OAuth2"
 
@@ -348,18 +345,9 @@ msgstr "Wystąpił problem podczas zapisywania konfiguracji do pliku konfiguracy
 msgid "Given user already exists in config file."
 msgstr "Podany użytkownik już istnieje w pliku konfiguracyjnym."
 
-msgid "List HTTP Basic users"
-msgstr "Lista użytkowników HTTP Basic"
-
-msgid "List OAuth2 clients"
-msgstr "Lista klientów OAuth2"
-
 msgid "set all scopes"
 msgstr "ustaw wszystkie zakresy"
 
-msgid "Go to configuration wizard"
-msgstr "Idź do przewodnika konfiguracji"
-
 msgid "Welcome on the Baculum API default page"
 msgstr "Witaj na stronie głównej Baculum API"
 
@@ -432,8 +420,8 @@ msgstr "Uwierzytelnianie do Baculum REST API"
 msgid "Authentication type:"
 msgstr "Typ uwierzytelniania:"
 
-msgid "Step 6 - authentication to API"
-msgstr "Krok 6 - uwierzytelnianie do API"
+msgid "Step 5 - authentication to API"
+msgstr "Krok 5 - uwierzytelnianie do API"
 
 msgid "Actions"
 msgstr "Akcje"
@@ -513,3 +501,71 @@ msgstr "Uwaga"
 msgid "Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file."
 msgstr "Do dodania tej konfiguracji proszę użyć visudo, w przeciwnym razie proszę pamiętać o tym, aby dodać pustą linię na końcu pliku."
 
+msgid "Dashboard"
+msgstr "Start"
+
+msgid "Basic users"
+msgstr "Basic users"
+
+msgid "OAuth2 clients"
+msgstr "OAuth2 clients"
+
+msgid "Settings"
+msgstr "Settings"
+
+msgid "Configuration wizard"
+msgstr "Configuration wizard"
+
+msgid "Welcome"
+msgstr "Witaj"
+
+msgid "Add user"
+msgstr "Dodaj użytkownika"
+
+msgid "Edit user"
+msgstr "Edytuj użytkownika"
+
+msgid "Add OAuth2 client"
+msgstr "Dodaj klienta OAuth2"
+
+msgid "Are you sure?"
+msgstr "Czy jesteś pewien?"
+
+msgid "API settings"
+msgstr "Ustawienia API"
+
+msgid "General"
+msgstr "Ogólne"
+
+msgid "Catalog"
+msgstr "Baza danych"
+
+msgid "Console"
+msgstr "Konsola"
+
+msgid "Config"
+msgstr "Konfiguracja"
+
+msgid "Enabled:"
+msgstr "Włączone:"
+
+msgid "Debug:"
+msgstr "Debug:"
+
+msgid "Add client"
+msgstr "Dodaj klienta"
+
+msgid "Edit client"
+msgstr "Edytuj klienta"
+
+msgid "Invalid user. User may contain a-z A-Z 0-9 characters."
+msgstr "Niepoprawna nazwa użytkownika. Użytkownik może zawierać znaki: a-z A-Z 0-9"
+
+msgid "Show/hide"
+msgstr "Pokaż/ukryj"
+
+msgid "Copy to clipboard"
+msgstr "Kopiuj do schowka"
+
+msgid "Logout"
+msgstr "Wyloguj się"
index 4ba9c6880d2f9f989c6fbc2ea02fb518a74f79cd..6542555825eb8acf8c7f20a962fc1b30cb66b990 100644 (file)
Binary files a/gui/baculum/protected/API/Lang/pt/messages.mo and b/gui/baculum/protected/API/Lang/pt/messages.mo differ
index ad3d71e3f455a41fe454c1e8ba1257e009b5fa44..d764ea724207ca31743d559af68cf4cf6d202359 100644 (file)
@@ -24,14 +24,14 @@ msgstr "Idioma"
 msgid "select language"
 msgstr "selecione o idioma"
 
-msgid "&laquo; Previous"
-msgstr "&laquo; Voltar"
+msgid "Previous"
+msgstr "Voltar"
 
 msgid "Cancel"
 msgstr "Cancelar"
 
-msgid "Next &raquo;"
-msgstr "Avançar &raquo;"
+msgid "Next"
+msgstr "Avançar"
 
 msgid "Step 1 - select language"
 msgstr "Passo 1 - Selecionar o idioma"
@@ -234,8 +234,8 @@ msgstr "Utilizar sudo para executar bconsole:"
 msgid "Save"
 msgstr "Salvar"
 
-msgid "Step 7 - Finish"
-msgstr "Passo 7 - Finalizar"
+msgid "Step 6 - Finish"
+msgstr "Passo 6 - Finalizar"
 
 msgid "Use sudo for Bacula JSON tools:"
 msgstr "Utilizar o sudo para os utilitários JSON:"
@@ -315,9 +315,6 @@ msgstr "Não se esqueça de desativar a autenticação HTTP Basic na configuraç
 msgid "Refresh token"
 msgstr "Atualizar token"
 
-msgid "New HTTP Basic user"
-msgstr "Novo usuário HTTP Básico"
-
 msgid "New OAuth2 client"
 msgstr "Novo cliente OAuth2"
 
@@ -348,18 +345,9 @@ msgstr "Problema durante a gravação do arquivo de configuração. Verifique a
 msgid "Given user already exists in config file."
 msgstr "O usuário já existe no arquivo de configuração."
 
-msgid "List HTTP Basic users"
-msgstr "Listar usuários HTTP Básico"
-
-msgid "List OAuth2 clients"
-msgstr "Listar usuários OAuth2"
-
 msgid "set all scopes"
 msgstr "definir todos os escopos"
 
-msgid "Go to configuration wizard"
-msgstr "Ir para o assistente de configuração"
-
 msgid "Welcome on the Baculum API default page"
 msgstr "Bem-vindo à página padrão do Baculum API"
 
@@ -432,8 +420,8 @@ msgstr "Autenticação REST API do Baculum"
 msgid "Authentication type:"
 msgstr "Tipo de autenticação:"
 
-msgid "Step 6 - authentication to API"
-msgstr "Passo 6 - Autenticação da API"
+msgid "Step 5 - authentication to API"
+msgstr "Passo 5 - Autenticação da API"
 
 msgid "Actions"
 msgstr "Ações"
@@ -513,3 +501,71 @@ msgstr "Nota"
 msgid "Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file."
 msgstr "Por favor, use o visudo para adicionar essa configuração, caso contrário, lembre-se de adicionar uma linha vazia no final do arquivo."
 
+msgid "Dashboard"
+msgstr "Painel de controle"
+
+msgid "Basic users"
+msgstr "Basic users"
+
+msgid "OAuth2 clients"
+msgstr "OAuth2 clients"
+
+msgid "Settings"
+msgstr "Settings"
+
+msgid "Configuration wizard"
+msgstr "Configuration wizard"
+
+msgid "Welcome"
+msgstr "Seja bem vindo"
+
+msgid "Add user"
+msgstr "Adicionar usuário"
+
+msgid "Edit user"
+msgstr "Edit user"
+
+msgid "Add OAuth2 client"
+msgstr "Add OAuth2 client"
+
+msgid "Are you sure?"
+msgstr "Are you sure?"
+
+msgid "API settings"
+msgstr "API settings"
+
+msgid "General"
+msgstr "General"
+
+msgid "Catalog"
+msgstr "Catalog"
+
+msgid "Console"
+msgstr "Console"
+
+msgid "Config"
+msgstr "Config"
+
+msgid "Enabled:"
+msgstr "Enabled:"
+
+msgid "Debug:"
+msgstr "Debug:"
+
+msgid "Add client"
+msgstr "Add client"
+
+msgid "Edit client"
+msgstr "Edit client"
+
+msgid "Invalid user. User may contain a-z A-Z 0-9 characters."
+msgstr "Invalid user. User may contain a-z A-Z 0-9 characters."
+
+msgid "Show/hide"
+msgstr "Show/hide"
+
+msgid "Copy to clipboard"
+msgstr "Copy to clipboard"
+
+msgid "Logout"
+msgstr "Logout"
index 1a7a6be2bff839b5eea155b6a0340d784fd3c19a..b728cd0a5017df760148d31db13e913addf24ce6 100644 (file)
Binary files a/gui/baculum/protected/API/Lang/ru/messages.mo and b/gui/baculum/protected/API/Lang/ru/messages.mo differ
index c8a21ca4a682c36142d1a7af5b4bf8f011a09e97..c538812f6fffb77f7f27c97bc193a91434cb3281 100644 (file)
@@ -26,14 +26,14 @@ msgstr "Язык"
 msgid "select language"
 msgstr "выберите язык"
 
-msgid "&laquo; Previous"
-msgstr "&laquo; Назад"
+msgid "Previous"
+msgstr "Назад"
 
 msgid "Cancel"
 msgstr "Отмена"
 
-msgid "Next &raquo;"
-msgstr "Дальше &raquo;"
+msgid "Next"
+msgstr "Дальше"
 
 msgid "Step 1 - select language"
 msgstr "Шаг 1 - выбор языка"
@@ -236,8 +236,8 @@ msgstr "Использовать sudo для запросов bconsole:"
 msgid "Save"
 msgstr "Сохранить"
 
-msgid "Step 7 - Finish"
-msgstr "Шаг 7 - Завершение"
+msgid "Step 6 - Finish"
+msgstr "Шаг 6 - Завершение"
 
 msgid "Use sudo for Bacula JSON tools:"
 msgstr "Использовать sudo для инструментов Bacula JSON:"
@@ -317,9 +317,6 @@ msgstr "Не забудьте отключить базовую аутентиф
 msgid "Refresh token"
 msgstr "Обновить токен"
 
-msgid "New HTTP Basic user"
-msgstr "Новый базовый пользователь HTTP"
-
 msgid "New OAuth2 client"
 msgstr "Новый OAuth2 клиент"
 
@@ -350,18 +347,9 @@ msgstr "Возникла проблема при сохранении конфи
 msgid "Given user already exists in config file."
 msgstr "Данный пользователь уже существует в файле конфигурации."
 
-msgid "List HTTP Basic users"
-msgstr "Список базовых пользователей HTTP"
-
-msgid "List OAuth2 clients"
-msgstr "Список OAuth2 клиентов"
-
 msgid "set all scopes"
 msgstr "установить все области"
 
-msgid "Go to configuration wizard"
-msgstr "Перейти к мастеру настройки"
-
 msgid "Welcome on the Baculum API default page"
 msgstr "Добро пожаловать. Страница по умолчанию Baculum API."
 
@@ -434,8 +422,8 @@ msgstr "Аутентификация в Baculum REST API"
 msgid "Authentication type:"
 msgstr "Тип аутентификации:"
 
-msgid "Step 6 - authentication to API"
-msgstr "Шаг 6 - аутентификация в API"
+msgid "Step 5 - authentication to API"
+msgstr "Шаг 5 - аутентификация в API"
 
 msgid "Actions"
 msgstr "Действия"
@@ -515,3 +503,71 @@ msgstr "Примечание"
 msgid "Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file."
 msgstr "Пожалуйста, используйте visudo для добавления этой конфигурации, в противном случае не забудьте добавить пустую строку в конце файла."
 
+msgid "Dashboard"
+msgstr "Панель управления"
+
+msgid "Basic users"
+msgstr "Basic users"
+
+msgid "OAuth2 clients"
+msgstr "OAuth2 clients"
+
+msgid "Settings"
+msgstr "Settings"
+
+msgid "Configuration wizard"
+msgstr "Configuration wizard"
+
+msgid "Welcome"
+msgstr "Добро пожаловать"
+
+msgid "Add user"
+msgstr "Добавить пользователя"
+
+msgid "Edit user"
+msgstr "Edit user"
+
+msgid "Add OAuth2 client"
+msgstr "Add OAuth2 client"
+
+msgid "Are you sure?"
+msgstr "Are you sure?"
+
+msgid "API settings"
+msgstr "API settings"
+
+msgid "General"
+msgstr "General"
+
+msgid "Catalog"
+msgstr "Catalog"
+
+msgid "Console"
+msgstr "Console"
+
+msgid "Config"
+msgstr "Config"
+
+msgid "Enabled:"
+msgstr "Enabled:"
+
+msgid "Debug:"
+msgstr "Debug:"
+
+msgid "Add client"
+msgstr "Add client"
+
+msgid "Edit client"
+msgstr "Edit client"
+
+msgid "Invalid user. User may contain a-z A-Z 0-9 characters."
+msgstr "Invalid user. User may contain a-z A-Z 0-9 characters."
+
+msgid "Show/hide"
+msgstr "Show/hide"
+
+msgid "Copy to clipboard"
+msgstr "Copy to clipboard"
+
+msgid "Logout"
+msgstr "Logout"
index ba24a0c882de1216e7e406cafcedf8c89f5e257e..d7821667e69df4d3edff799f0187e08c385492ac 100644 (file)
@@ -1,11 +1,41 @@
 <!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-       <com:THead Title="Baculum - Bacula Web Interface" ShortcutIcon="<%=$this->getPage()->getTheme()->getBaseUrl()%>/favicon.ico" />
-       <body class="api">
+<html lang="en">
+       <com:THead Title="Baculum - Bacula Web Interface">
+       <meta charset="utf-8" />
+       <meta name="viewport" content="width=device-width, initial-scale=1" />
+       <link rel="icon" href="<%=$this->getPage()->getTheme()->getBaseUrl()%>/favicon.ico" type="image/x-icon" />
+       </com:THead>
+       <body  class="w3-light-grey">
                <com:TForm>
-                       <com:TClientScript PradoScripts="effects" />
-                       <com:TContentPlaceHolder ID="Main" />
+                       <com:TClientScript PradoScripts="ajax, effects" />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/fontawesome.min.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/datatables.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/dataTables.responsive.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/responsive.jqueryui.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/dataTables.buttons.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/buttons.html5.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/buttons.colVis.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/dataTables.select.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/misc.js %> />
+                       <!-- Top container -->
+                       <div class="w3-bar w3-top w3-black w3-large" style="z-index: 4">
+                               <button type="button" class="w3-bar-item w3-button w3-hover-none w3-hover-text-light-grey" onclick="W3SideBar.open();"><i class="fa fa-bars"></i>  Menu</button>
+                               <span class="w3-bar-item w3-right">
+                                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/logo.png" alt="" />
+                               </span>
+                       </div>
+                       <com:Application.API.Portlets.APISideBar />
+                       <div class="w3-main page_main_el" id="page_main" style="margin-left: 250px; margin-top: 43px;">
+                               <com:TContentPlaceHolder ID="Main" />
+                               <footer class="w3-container w3-right-align w3-small"><%[ Version: ]%> <%=Params::BACULUM_VERSION%></footer>
+                       </div>
+                       <div id="small" class="w3-hide-large"></div>
                </com:TForm>
-               <footer class="footer"><%[ Version: ]%> <%=Params::BACULUM_VERSION%></footer>
+<script type="text/javascript">
+var is_small = $('#small').is(':visible');
+if (is_small) {
+       W3SideBar.close();
+}
+</script>
        </body>
 </html>
index 0966bbcb5cdf86b91db8a8126b761b672d816e3e..2e7e5a5f7e9f39758cf173f51669d14fba5bfce6 100644 (file)
@@ -3,6 +3,7 @@
        <com:THead Title="Baculum - Bacula Web Interface" ShortcutIcon="<%=$this->getPage()->getTheme()->getBaseUrl()%>/favicon.ico" />
        <body>
                <com:TForm>
+                               <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/misc.js %> />
                                <com:TClientScript PradoScripts="effects" />
                                <com:TContentPlaceHolder ID="Wizard" />
                </com:TForm>
index 1c2b09282dfba6e3dcc5c9c6995cf0d679d1a951..46a40d2e03cfe62d520dd02b32f8f2cf1fd8e618 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
@@ -20,6 +20,8 @@
  * Bacula(R) is a registered trademark of Kern Sibbald.
  */
  
+Prado::using('Application.API.Class.ConsoleOutputPage');
+
 /**
  * Client status.
  *
  * @category API
  * @package Baculum API
  */
-class ClientStatus extends BaculumAPIServer {
+class ClientStatus extends ConsoleOutputPage {
 
        public function get() {
                $clientid = $this->Request->contains('id') ? intval($this->Request['id']) : 0;
                $client = $this->getModule('client')->getClientById($clientid);
-               $result = $this->getModule('bconsole')->bconsoleCommand($this->director, array('.client'));
+               $status = $this->getModule('status_fd');
+               $type = $this->Request->contains('type') && $status->isValidOutputType($this->Request['type']) ? $this->Request['type'] : null;
+               $out_format = $this->Request->contains('output') && $this->isOutputFormatValid($this->Request['output']) ? $this->Request['output'] : parent::OUTPUT_FORMAT_RAW;
+               $result = $this->getModule('bconsole')->bconsoleCommand(
+                       $this->director,
+                       array('.client'),
+                       null,
+                       true
+               );
+
+               $client_exists = false;
                if ($result->exitcode === 0) {
-                       array_shift($result->output);
-                       if(is_object($client) && in_array($client->name, $result->output)) {
-                               $result = $this->getModule('bconsole')->bconsoleCommand(
-                                       $this->director,
-                                       array('status', 'client="' . $client->name . '"')
-                               );
-                               $this->output = $result->output;
-                               $this->error = $result->exitcode;
-                       } else {
-                               $this->output = ClientError::MSG_ERROR_CLIENT_DOES_NOT_EXISTS;
-                               $this->error = ClientError::ERROR_CLIENT_DOES_NOT_EXISTS;
-                       }
-               } else {
-                       $this->output = $result->output;
-                       $this->error = $result->exitcode;
+                       $client_exists = (is_object($client) && in_array($client->name, $result->output));
+               }
+
+               if ($client_exists == false) {
+                       // Client doesn't exist or is not available for user because of ACL restrictions
+                       $this->output = ClientError::MSG_ERROR_CLIENT_DOES_NOT_EXISTS;
+                       $this->error = ClientError::ERROR_CLIENT_DOES_NOT_EXISTS;
+                       return;
+               }
+               $out = (object)['output' => [], 'error' => 0];
+               if ($out_format === parent::OUTPUT_FORMAT_RAW) {
+                       $out = $this->getRawOutput(['client' => $client->name]);
+               } elseif ($out_format === parent::OUTPUT_FORMAT_JSON) {
+                       $out = $this->getJSONOutput([
+                               'client' => $client->name,
+                               'type' => $type
+                       ]);
                }
+               $this->output = $out['output'];
+               $this->error = $out['error'];
+       }
+
+       protected function getRawOutput($params = []) {
+               // traditional status client output
+               $result = $this->getModule('bconsole')->bconsoleCommand(
+                       $this->director,
+                       [
+                               'status',
+                               'client="' . $params['client'] . '"'
+                       ]
+               );
+               $error = $result->exitcode == 0 ? $result->exitcode : GenericError::ERROR_WRONG_EXITCODE;
+               $ret = [
+                       'output' => $result->output,
+                       'error' => $error
+               ];
+               return $ret;
+       }
+
+       protected function getJSONOutput($params = []) {
+               // status client JSON output by API 2 interface
+               $status = $this->getModule('status_fd');
+               return $status->getStatus(
+                       $this->director,
+                       $params['client'],
+                       $params['type']
+               );
        }
 }
 
diff --git a/gui/baculum/protected/API/Pages/API/DirectorStatus.php b/gui/baculum/protected/API/Pages/API/DirectorStatus.php
new file mode 100644 (file)
index 0000000..8daa0d5
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.API.Class.ConsoleOutputPage');
+
+/**
+ * Director status command endpoint.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category API
+ * @package Baculum API
+ */
+class DirectorStatus extends ConsoleOutputPage {
+
+       public function get() {
+               $status = $this->getModule('status_dir');
+               $director = $this->Request->contains('name') && $this->getModule('misc')->isValidName($this->Request['name']) ? $this->Request['name'] : null;
+               $type = $this->Request->contains('type') && $status->isValidOutputType($this->Request['type']) ? $this->Request['type'] : null;
+               $out_format = $this->Request->contains('output') && $this->isOutputFormatValid($this->Request['output']) ? $this->Request['output'] : parent::OUTPUT_FORMAT_RAW;
+
+               if (is_null($director)) {
+                       // Invalid director
+                       $this->output = BconsoleError::MSG_ERROR_INVALID_DIRECTOR;
+                       $this->error = BconsoleError::ERROR_INVALID_DIRECTOR;
+                       return;
+               }
+
+               $out = (object)['output' => [], 'error' => 0];
+               if ($out_format === parent::OUTPUT_FORMAT_RAW) {
+                       $out = $this->getRawOutput(['director' => $director]);
+               } elseif ($out_format === parent::OUTPUT_FORMAT_JSON) {
+                       $out = $this->getJSONOutput([
+                               'director' => $director,
+                               'type' => $type
+                       ]);
+               }
+               $this->output = $out['output'];
+               $this->error = $out['error'];
+       }
+
+       protected function getRawOutput($params = []) {
+               // traditional status director output
+               $result = $this->getModule('bconsole')->bconsoleCommand(
+                       $params['director'],
+                       [
+                               'status',
+                               'director'
+                       ]
+               );
+               $error = $result->exitcode == 0 ? $result->exitcode : GenericError::ERROR_WRONG_EXITCODE;
+               $ret = [
+                       'output' => $result->output,
+                       'error' => $error
+               ];
+               return $ret;
+       }
+
+       protected function getJSONOutput($params = []) {
+               // status director JSON output by API 2 interface
+               $status = $this->getModule('status_dir');
+               return $status->getStatus(
+                       $params['director'],
+                       null,
+                       $params['type']
+               );
+       }
+}
+
+?>
index 42b60deaaa6feb122a6bfafa08c0f2219f6098eb..8874e44f413f4dd5b756786c78882cdae4be79bc 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
@@ -19,6 +19,8 @@
  *
  * Bacula(R) is a registered trademark of Kern Sibbald.
  */
+
+Prado::using('Application.API.Class.ConsoleOutputPage');
  
 /**
  * Storage status command endpoint.
  * @category API
  * @package Baculum API
  */
-class StorageStatus extends BaculumAPIServer {
+class StorageStatus extends ConsoleOutputPage {
+
        public function get() {
                $storageid = $this->Request->contains('id') ? intval($this->Request['id']) : 0;
                $storage = $this->getModule('storage')->getStorageById($storageid);
-
+               $status = $this->getModule('status_sd');
+               $type = $this->Request->contains('type') && $status->isValidOutputType($this->Request['type']) ? $this->Request['type'] : null;
+               $out_format = $this->Request->contains('output') && $this->isOutputFormatValid($this->Request['output']) ? $this->Request['output'] : parent::OUTPUT_FORMAT_RAW;
                $result = $this->getModule('bconsole')->bconsoleCommand(
                        $this->director,
                        array('.storage')
                );
+
+               $storage_exists = false;
                if ($result->exitcode === 0) {
-                       array_shift($result->output);
-                       $storage = $this->getModule('storage')->getStorageById($storageid);
-                       if (is_object($storage) && in_array($storage->name, $result->output)) {
-                               $result = $this->getModule('bconsole')->bconsoleCommand(
-                                       $this->director,
-                                       array('status', 'storage="' . $storage->name . '"')
-                               );
-                               $this->output = $result->output;
-                               $this->error = $result->exitcode;
-                       } else {
-                               $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS;
-                               $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS;
-                       }
-               } else {
-                       $this->output = $result->output;
-                       $this->error = $result->exitcode;
+                       $storage_exists = (is_object($storage) && in_array($storage->name, $result->output));
                }
+
+               if ($storage_exists == false) {
+                       // Storage doesn't exist or is not available for user because of ACL restrictions
+                       $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS;
+                       $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS;
+                       return;
+               }
+
+               $out = (object)['output' => [], 'error' => 0];
+               if ($out_format === parent::OUTPUT_FORMAT_RAW) {
+                       $out = $this->getRawOutput(['storage' => $storage->name]);
+               } elseif ($out_format === parent::OUTPUT_FORMAT_JSON) {
+                       $out = $this->getJSONOutput([
+                               'storage' => $storage->name,
+                               'type' => $type
+                       ]);
+               }
+               $this->output = $out['output'];
+               $this->error = $out['error'];
+       }
+
+       protected function getRawOutput($params = []) {
+               // traditional status storage output
+               $result = $this->getModule('bconsole')->bconsoleCommand(
+                       $this->director,
+                       [
+                               'status',
+                               'storage="' . $params['storage'] . '"'
+                       ]
+               );
+               $error = $result->exitcode == 0 ? $result->exitcode : GenericError::ERROR_WRONG_EXITCODE;
+               $ret = [
+                       'output' => $result->output,
+                       'error' => $error
+               ];
+               return $ret;
+       }
+
+       protected function getJSONOutput($params = []) {
+               // status storage JSON output by API 2 interface
+               $status = $this->getModule('status_sd');
+               return $status->getStatus(
+                       $this->director,
+                       $params['storage'],
+                       $params['type']
+               );
        }
 }
 
index 766f2af5ae7c5d1a7a6b808fe06c57cb40c0d520..6381b73f5328563ee7020882a0888123a64e0dbd 100644 (file)
                <module id="oauth2_authid" class="Application.API.Class.OAuth2.AuthIdManager" />
                <module id="oauth2_token" class="Application.API.Class.OAuth2.TokenManager" />
 
+               <!-- API Server modules -->
+               <module id="api_server_v1" class="Application.API.Class.APIServerV1" />
+               <module id="api_server_v2" class="Application.API.Class.APIServerV2" />
+
                <!-- database modules -->
                <module id="db" class="Application.API.Class.Database" />
                <module id="client" class="Application.API.Class.ClientManager" />
index 2b18c75309ecfce1ceca4785acae329793062353..bccbc1e1972561452097ccb136069b714d136bbe 100644 (file)
@@ -6,6 +6,102 @@
        <url ServiceParameter="Authorize" pattern="api/auth/" />
        <url ServiceParameter="RequestToken" pattern="api/token/" />
 
+       <!-- API v2 -->
+       <!-- general endpoint -->
+       <url ServiceParameter="Welcome" pattern="api/v2/welcome/" />
+       <!-- bconsole endpoints -->
+       <url ServiceParameter="ConsoleCommand" pattern="api/v2/console/" />
+       <!-- database endpoints -->
+       <url ServiceParameter="Catalog" pattern="api/v2/catalog/" />
+       <url ServiceParameter="DbSize" pattern="api/v2/dbsize/" />
+       <!-- director endpoints -->
+       <url ServiceParameter="Directors" pattern="api/v2/directors/" />
+       <!-- clients (file daemons) endpoints -->
+       <url ServiceParameter="Clients" pattern="api/v2/clients/" />
+       <url ServiceParameter="Client" pattern="api/v2/clients/{id}/" parameters.id="\d+" />
+       <url ServiceParameter="ClientsShow" pattern="api/v2/clients/show/" />
+       <url ServiceParameter="ClientShow" pattern="api/v2/clients/{id}/show/" parameters.id="\d+" />
+       <url ServiceParameter="ClientStatus" pattern="api/v2/clients/{id}/status/" parameters.id="\d+" />
+       <url ServiceParameter="JobsForClient" pattern="api/v2/clients/{id}/jobs/" parameters.id="\d+" />
+       <url ServiceParameter="ClientLs" pattern="api/v2/clients/{id}/ls/" parameters.id="\d+" />
+       <url ServiceParameter="ClientBandwidthLimit" pattern="api/v2/clients/{id}/bandwidth/" parameters.id="\d+" />
+       <!-- storages (storage daemons) endpoints -->
+       <url ServiceParameter="Storages" pattern="api/v2/storages/" />
+       <url ServiceParameter="Storage" pattern="api/v2/storages/{id}/" parameters.id="\d+" />
+       <url ServiceParameter="StoragesShow" pattern="api/v2/storages/show/" />
+       <url ServiceParameter="StorageShow" pattern="api/v2/storages/{id}/show/" parameters.id="\d+" />
+       <url ServiceParameter="StorageStatus" pattern="api/v2/storages/{id}/status/" parameters.id="\d+" />
+       <url ServiceParameter="StorageMount" pattern="api/v2/storages/{id}/mount/" parameters.id="\d+" />
+       <url ServiceParameter="StorageUmount" pattern="api/v2/storages/{id}/umount/" parameters.id="\d+" />
+       <url ServiceParameter="StorageRelease" pattern="api/v2/storages/{id}/release/" parameters.id="\d+" />
+       <!-- volumes (media) endpoints-->
+       <url ServiceParameter="Volumes" pattern="api/v2/volumes/" />
+       <url ServiceParameter="Volume" pattern="api/v2/volumes/{id}/" parameters.id="\d+" />
+       <url ServiceParameter="VolumePrune" pattern="api/v2/volumes/{id}/prune/" parameters.id="\d+" />
+       <url ServiceParameter="VolumePurge" pattern="api/v2/volumes/{id}/purge/" parameters.id="\d+" />
+       <url ServiceParameter="VolumesRequired" pattern="api/v2/volumes/required/{jobid}/{fileid}/" parameters.jobid="\d+" parameters.fileid="\d+" />
+       <url ServiceParameter="JobsOnVolume" pattern="api/v2/volumes/{id}/jobs/" parameters.id="\d+" />
+       <url ServiceParameter="VolumeLabel" pattern="api/v2/volumes/label/" />
+       <url ServiceParameter="VolumeLabelBarcodes" pattern="api/v2/volumes/label/barcodes/" />
+       <url ServiceParameter="SlotsUpdate" pattern="api/v2/volumes/update/" />
+       <url ServiceParameter="SlotsUpdate" pattern="api/v2/volumes/update/{barcodes}/" parameters.barcodes="barcodes" />
+       <!-- pools endpoints -->
+       <url ServiceParameter="Pools" pattern="api/v2/pools/" />
+       <url ServiceParameter="Pool" pattern="api/v2/pools/{id}/" parameters.id="\d+" />
+       <url ServiceParameter="VolumesInPool" pattern="api/v2/pools/{id}/volumes/" parameters.id="\d+" />
+       <url ServiceParameter="PoolUpdate" pattern="api/v2/pools/{id}/update/" parameters.id="\d+" />
+       <url ServiceParameter="PoolUpdateVolumes" pattern="api/v2/pools/{id}/update/volumes/" parameters.id="\d+" />
+       <url ServiceParameter="PoolsShow" pattern="api/v2/pools/show/" />
+       <url ServiceParameter="PoolShow" pattern="api/v2/pools/{id}/show/" parameters.id="\d+" />
+       <!-- jobs endpoints-->
+       <url ServiceParameter="Jobs" pattern="api/v2/jobs/" />
+       <url ServiceParameter="Job" pattern="api/v2/jobs/{id}/" parameters.id="\d+" />
+       <url ServiceParameter="JobResNames" pattern="api/v2/jobs/resnames/" />
+       <url ServiceParameter="JobsShow" pattern="api/v2/jobs/show/" />
+       <url ServiceParameter="JobShow" pattern="api/v2/jobs/{id}/show/" parameters.id="\d+" />
+       <url ServiceParameter="JobBandwidthLimit" pattern="api/v2/jobs/{id}/bandwidth/" parameters.id="\d+" />
+       <url ServiceParameter="JobsRecent" pattern="api/v2/jobs/recent/{name}/" parameters.name="[a-zA-Z0-9:.\-_ ]+" />
+       <url ServiceParameter="JobEstimate" pattern="api/v2/jobs/estimate/" />
+       <url ServiceParameter="JobRun" pattern="api/v2/jobs/run/" />
+       <url ServiceParameter="JobCancel" pattern="api/v2/jobs/{id}/cancel/" parameters.id="\d+"/>
+       <url ServiceParameter="JobTotals" pattern="api/v2/jobs/totals/" />
+       <url ServiceParameter="JobListFiles" pattern="api/v2/jobs/{id}/files/" parameters.id="\d+" />
+       <url ServiceParameter="JobFiles" pattern="api/v2/jobs/files/" />
+       <url ServiceParameter="RestoreRun" pattern="api/v2/jobs/restore/" />
+       <!-- bvfs endpoints-->
+       <url ServiceParameter="BVFSUpdate" pattern="api/v2/bvfs/update/" />
+       <url ServiceParameter="BVFSLsDirs" pattern="api/v2/bvfs/lsdirs/" />
+       <url ServiceParameter="BVFSLsFiles" pattern="api/v2/bvfs/lsfiles/" />
+       <url ServiceParameter="BVFSVersions" pattern="api/v2/bvfs/versions/" />
+       <url ServiceParameter="BVFSGetJobids" pattern="api/v2/bvfs/getjobids/" />
+       <url ServiceParameter="BVFSRestore" pattern="api/v2/bvfs/restore/" />
+       <url ServiceParameter="BVFSClearCache" pattern="api/v2/bvfs/clear/" />
+       <url ServiceParameter="BVFSCleanUp" pattern="api/v2/bvfs/cleanup/" />
+       <!-- joblog endpoints -->
+       <url ServiceParameter="JobLog" pattern="api/v2/joblog/{id}/" parameters.id="\d+" />
+       <!-- @TODO: Separate this endpoint outside 'joblog' -->
+       <url ServiceParameter="Messages" pattern="api/v2/joblog/messages" />
+       <!-- fileset endpoints -->
+       <url ServiceParameter="FileSets" pattern="api/v2/filesets/" />
+       <url ServiceParameter="FileSet" pattern="api/v2/filesets/{id}/" parameters.id="\d+" />
+       <url ServiceParameter="FileSetResNames" pattern="api/v2/filesets/resnames/" />
+       <!-- schedule endpoints -->
+       <url ServiceParameter="Schedules" pattern="api/v2/schedules/resnames/" />
+       <url ServiceParameter="ScheduleStatus" pattern="api/v2/schedules/status/" />
+       <!-- Bacula config module endpoints -->
+       <url ServiceParameter="Config" pattern="api/v2/config/" />
+       <url ServiceParameter="Config" pattern="api/v2/config/{component_type}/" parameters.component_type="[a-z]+" />
+       <url ServiceParameter="Config" pattern="api/v2/config/{component_type}/{resource_type}/" parameters.component_type="[a-z]+" parameters.resource_type="[a-zA-Z]+" />
+       <url ServiceParameter="Config" pattern="api/v2/config/{component_type}/{resource_type}/{resource_name}/" parameters.component_type="[a-z]+" parameters.resource_type="[a-zA-Z]+" parameters.resource_name="[a-zA-Z0-9:.\-_ ]+" />
+       <!-- director endpoints -->
+       <url ServiceParameter="DirectorStatus" pattern="api/v2/directors/{name}/status/" parameters.name="[a-zA-Z0-9:.\-_ ]+" />
+       <!-- actions endpoints -->
+       <url ServiceParameter="Actions" pattern="api/v2/actions/{component}/{action}/" parameters.component="(director|storage|client)" parameters.action="(start|stop|restart)" />
+       <!-- OAuth2 client endpoints -->
+       <url ServiceParameter="OAuth2Clients" pattern="api/v2/oauth2/clients/" />
+       <url ServiceParameter="OAuth2Client" pattern="api/v2/oauth2/clients/{id}/" parameters.id="[a-zA-Z0-9\-_]{32}" />
+
+       <!-- API v1 -->
        <!-- general endpoint -->
        <url ServiceParameter="Welcome" pattern="api/v1/welcome/" />
        <!-- bconsole endpoints -->
diff --git a/gui/baculum/protected/API/Pages/Panel/APIBasicUsers.php b/gui/baculum/protected/API/Pages/Panel/APIBasicUsers.php
new file mode 100644 (file)
index 0000000..e4688b0
--- /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.ActiveControls.TCallback');
+Prado::using('Application.API.Class.BaculumAPIPage');
+
+/**
+ * API Basic users page.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Panel
+ * @package Baculum API
+ */
+class APIBasicUsers extends BaculumAPIPage {
+
+       public function onInit($param) {
+               parent::onInit($param);
+
+               $config = $this->getModule('api_config')->getConfig();
+               if(count($config) === 0) {
+                       // Config doesn't exist, go to wizard
+                       $this->goToPage('APIInstallWizard');
+                       return;
+               } elseif (!$this->IsCallback) {
+                       $this->loadBasicUsers(null, null);
+               }
+       }
+
+       public function loadBasicUsers($sender, $param) {
+               $users = $this->getBasicUsers();
+               $this->getCallbackClient()->callClientFunction(
+                       'oAPIBasicUsers.load_basic_users_cb',
+                       [$users]
+               );
+               $this->hideBasicUserWindow($sender);
+       }
+
+       public function cancelBasicUserWindow($sender, $param) {
+               $this->hideBasicUserWindow($sender);
+       }
+
+       private function hideBasicUserWindow($sender) {
+               if (is_object($sender)) {
+                       if ($sender->ID === 'NewBasicClient') {
+                               $this->getCallbackClient()->callClientFunction(
+                                       'oAPIBasicUsers.show_new_user_window',
+                                       [false]
+                               );
+                       } elseif ($sender->ID === 'EditBasicClient') {
+                               $this->getCallbackClient()->callClientFunction(
+                                       'oAPIBasicUsers.show_edit_user_window',
+                                       [false]
+                               );
+                       }
+               }
+       }
+
+       private function getBasicUsers() {
+               $basic_users = array();
+               $basic_cfg = $this->getModule('basic_apiuser')->getUsers();
+               foreach($basic_cfg as $user => $pwd) {
+                       $basic_users[] = ['username' => $user];
+               }
+               return $basic_users;
+       }
+
+       public function deleteBasicUser($sender, $param) {
+               $config = $this->getModule('basic_apiuser');
+               $username = $param->getCallbackParameter();
+               $config->removeUser($username);
+               $this->loadBasicUsers(null, null);
+       }
+}
+?>
diff --git a/gui/baculum/protected/API/Pages/Panel/APIBasicUsers.tpl b/gui/baculum/protected/API/Pages/Panel/APIBasicUsers.tpl
new file mode 100644 (file)
index 0000000..f71ee2c
--- /dev/null
@@ -0,0 +1,218 @@
+<%@ MasterClass="Application.API.Layouts.Main" Theme="Baculum-v2"%>
+<com:TContent ID="Main">
+       <header class="w3-container w3-block">
+               <h5>
+                       <i class="fas fa-users"></i> <%[ Basic users ]%>
+               </h5>
+       </header>
+       <div class="w3-container">
+               <a href="javascript:void(0)" class="w3-button w3-green w3-margin-bottom" onclick="oAPIBasicUsers.show_new_user_window(true);">
+                       <i class="fas fa-plus"></i> &nbsp;<%[ Add user ]%>
+               </a>
+               <table id="basic_user_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+                       <thead>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Username ]%></th>
+                                       <th><%[ Actions ]%></th>
+                               </tr>
+                       </thead>
+                       <tbody id="basic_user_list_body"></tbody>
+                       <tfoot>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Username ]%></th>
+                                       <th><%[ Actions ]%></th>
+                               </tr>
+                       </tfoot>
+               </table>
+       </div>
+<script>
+var oBasicUserList = {
+       ids: {
+               basic_user_list: 'basic_user_list',
+               basic_user_list_body: 'basic_user_list_body'
+       },
+       table: null,
+       data: [],
+       init: function() {
+               if (!this.table) {
+                       this.set_table();
+               } else {
+                       var page = this.table.page();
+                       this.table.clear().rows.add(this.data).draw();
+                       this.table.page(page).draw(false);
+               }
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.basic_user_list).DataTable({
+                       data: this.data,
+                       deferRender: true,
+                       dom: 'lBfrtip',
+                       stateSave: true,
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
+                       columns: [
+                               {
+                                       className: 'details-control',
+                                       orderable: false,
+                                       data: null,
+                                       defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+                               },
+                               {data: 'username'},
+                               {
+                                       data: 'username',
+                                       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';
+                                               edit_btn.type = 'button';
+                                               var i = document.createElement('I');
+                                               i.className = 'fas fa-edit';
+                                               var label = document.createTextNode(' <%[ Edit ]%>');
+                                               edit_btn.appendChild(i);
+                                               edit_btn.innerHTML += '&nbsp';
+                                               edit_btn.appendChild(label);
+                                               edit_btn.setAttribute('onclick', 'oAPIBasicUsers.edit_user("' + data + '")');
+
+                                               span.appendChild(edit_btn);
+
+                                               if (this.data.length > 1) {
+                                                       var del_btn = document.createElement('BUTTON');
+                                                       del_btn.className = 'w3-button w3-red w3-margin-left';
+                                                       del_btn.type = 'button';
+                                                       var i = document.createElement('I');
+                                                       i.className = 'fas fa-trash-alt';
+                                                       var label = document.createTextNode(' <%[ Delete ]%>');
+                                                       del_btn.appendChild(i);
+                                                       del_btn.innerHTML += '&nbsp';
+                                                       del_btn.appendChild(label);
+                                                       del_btn.setAttribute('onclick', 'oAPIBasicUsers.delete_user("' + data + '")');
+
+                                                       span.appendChild(del_btn);
+                                               }
+                                               return span.outerHTML;
+                                       }.bind(this)
+                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: 'control',
+                               orderable: false,
+                               targets: 0
+                       },
+                       {
+                               className: "dt-center",
+                               targets: [ 2 ]
+                       }],
+                       order: [1, 'asc'],
+               });
+       }
+};
+</script>
+       <div id="new_basic_user_window" class="w3-modal">
+               <div class="w3-modal-content w3-animate-top w3-card-4">
+                       <header class="w3-container w3-teal">
+                               <span onclick="document.getElementById('new_basic_user_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                               <h2><%[ Add user ]%></h2>
+                       </header>
+                       <div class="w3-container w3-margin-left w3-margin-right w3-text-teal">
+                               <com:Application.Common.Portlets.NewAuthClient
+                                       ID="NewBasicClient"
+                                       Mode="add"
+                                       AuthType="basic"
+                                       OnSuccess="loadBasicUsers"
+                                       OnCancel="cancelBasicUserWindow"
+                               />
+                       </div>
+               </div>
+       </div>
+       <div id="edit_basic_user_window" class="w3-modal">
+               <div class="w3-modal-content w3-animate-top w3-card-4">
+                       <header class="w3-container w3-teal">
+                               <span onclick="document.getElementById('edit_basic_user_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                               <h2><%[ Edit user ]%></h2>
+                       </header>
+                       <div class="w3-container w3-margin-left w3-margin-right w3-text-teal">
+                               <com:Application.Common.Portlets.NewAuthClient
+                                       ID="EditBasicClient"
+                                       Mode="edit"
+                                       AuthType="basic"
+                                       OnSuccess="loadBasicUsers"
+                                       OnCancel="cancelBasicUserWindow"
+                               />
+                       </div>
+               </div>
+       </div>
+<com:TCallback ID="LoadUsers" OnCallback="loadBasicUsers" />
+<com:TCallback ID="DeleteUser" OnCallback="deleteBasicUser" />
+<script>
+var oAPIBasicUsers = {
+       ids: {
+               new_user_window: 'new_basic_user_window',
+               new_basic_user: '<%=$this->NewBasicClient->APIBasicLogin->ClientID%>',
+               edit_user_window: 'edit_basic_user_window',
+               edit_basic_pwd: '<%=$this->EditBasicClient->APIBasicPassword->ClientID%>'
+       },
+       new_obj: <%=$this->NewBasicClient->ClientID%>oNewAuthClient,
+       edit_obj: <%=$this->EditBasicClient->ClientID%>oNewAuthClient,
+       init: function() {
+               this.load_basic_users();
+       },
+       show_new_user_window: function(show) {
+               oAPIBasicUsers.new_obj.hide_errors();
+               oAPIBasicUsers.new_obj.clear_basic_fields();
+               var win = document.getElementById(oAPIBasicUsers.ids.new_user_window);
+               if (show) {
+                       win.style.display = 'block';
+               } else {
+                       win.style.display = 'none';
+               }
+               document.getElementById(oAPIBasicUsers.ids.new_basic_user).focus();
+       },
+       show_edit_user_window: function(show) {
+               oAPIBasicUsers.edit_obj.hide_errors();
+               var win = document.getElementById(oAPIBasicUsers.ids.edit_user_window);
+               if (show) {
+                       win.style.display = 'block';
+               } else {
+                       win.style.display = 'none';
+               }
+               document.getElementById(oAPIBasicUsers.ids.edit_basic_pwd).focus();
+       },
+       load_basic_users: function() {
+               var cb = <%=$this->LoadUsers->ActiveControl->Javascript%>;
+               cb.dispatch();
+       },
+       load_basic_users_cb: function(users) {
+               oBasicUserList.data = users;
+               oBasicUserList.init();
+       },
+       edit_user: function(username) {
+               this.edit_obj.clear_basic_fields();
+               this.edit_obj.set_basic_props({
+                       username: username
+               });
+               this.show_edit_user_window(true);
+       },
+       delete_user: function(username) {
+               if (!confirm('<%[ Are you sure? ]%>')) {
+                       return false;
+               }
+               var cb = <%=$this->DeleteUser->ActiveControl->Javascript%>;
+               cb.setCallbackParameter(username);
+               cb.dispatch();
+       }
+};
+$(function() {
+       oAPIBasicUsers.init();
+});
+</script>
+</com:TContent>
index 153b84beff5b8b997ff6ae53112271e690c0d11f..31f7cd4df77a4fa9d799cbf61dace0fcfc9564ce 100644 (file)
@@ -1,29 +1,37 @@
-<%@ MasterClass="Application.API.Layouts.Main" Theme="Baculum-v1"%>
+<%@ MasterClass="Application.API.Layouts.Main" Theme="Baculum-v2"%>
 <com:TContent ID="Main">
-       <com:BClientScript ScriptUrl=<%~ ../../JavaScript/misc.js %> />
-       <h3 style="clear: left"><%[ Welcome on the Baculum API default page ]%> <input type="button" class="bbutton" onclick="window.location.href='<%=$this->getService()->constructUrl('APIInstallWizard')%>'" value="<%[ Go to configuration wizard ]%>" style="float: right" /></h3>
-       <div id="tabs">
-               <ul>
-                       <li><a href="#tab1"><%[ Baculum API Client ]%></a></li>
-                       <li><a href="#tab2"><%[ List HTTP Basic users ]%></a></li>
-                       <li><a href="#tab3"><%[ List OAuth2 clients ]%></a></li>
-               </ul>
-               <div id="tab1">
+       <div class="w3-container">
+               <h3><%[ Welcome on the Baculum API default page ]%></h3>
+               <header class="w3-container w3-white">
+                       <h5>
+                               <i class="fas fa-user-lock"></i> <%[ Baculum API Client ]%>
+                       </h5>
+               </header>
+               <div class="w3-container w3-white">
                        <p><%[ Here you can try one from API commands ]%></p>
                        <div>
-                               <com:TJuiProgressbar ID="Progress" Options.Max="3" Options.Value="false" Width="700px" CssClass="center" Style="float: left">
+                               <com:TJuiProgressbar ID="Progress" Options.Max="3" Options.Value="false" Width="700px" CssClass="w3-center w3-margin-bottom" Style="float: left; position: relative">
                                        <span id="progress_label"><%[ Starting... ]%></span>
                                </com:TJuiProgressbar>
-                               <img id="api_refresh" src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_refresh.png" alt="<%[ Refresh token ]%>" title="<%[ Refresh token ]%>" onclick="oAPIHome.init_oauth2_client();" />
+                               <span  id="api_refresh" style="cursor: pointer">
+                                       <i class="w3-padding w3-large fas fa-sync-alt" title="<%[ Refresh token ]%>" onclick="oAPIHome.init_oauth2_client();"></i>
+                                       <i class="w3-padding-right w3-large fas fa-eye fa-fw" title="<%[ Show/hide ]%>" onclick="oAPIHome.show_hide_token(this);"></i>
+                                       <i class="w3-padding w3-large fas fa-copy" title="<%[ Copy to clipboard ]%>" onclick="copy_to_clipboard(oAPIHome.token);"></i>
+                               </span>
+
                        </div>
-                       <div class="line" id="auth_params_combo_container" style="margin-top: 10px; width: 700px; display: none">
-                               <div class="text"><com:TLabel ForControl="AuthParamsCombo" Text="<%[ Select: ]%>" /></div>
-                               <div class="field">
-                                       <com:TActiveDropDownList ID="AuthParamsCombo" AutoPostBack="false" />
+                       <div class="w3-row w3-section" id="auth_params_combo_container" style="margin-top: 10px; width: 700px; display: none">
+                               <div class="w3-quarter w3-center"><com:TLabel ForControl="AuthParamsCombo" Text="<%[ Select: ]%>" /></div>
+                               <div class="w3-rest">
+                                       <com:TActiveDropDownList
+                                               ID="AuthParamsCombo"
+                                               AutoPostBack="false"
+                                               CssClass="w3-select w3-border"
+                                       />
                                </div>
                        </div>
-                       <div style="clear: left; margin-top: 60px;">
-                               <select id="section" class="api_select">
+                       <div class="w3-row w3-section w3-margin-top">
+                               <select id="section" class="w3-select w3-border w3-left" style="width: 200px;">
                                        <option value="none"><%[ Please select API endpoint ]%></option>
                                        <option value="storages">storages</option>
                                        <option value="clients">clients</option>
                                        <option value="joblog">joblog</option>
                                        <option value="bvfs">bvfs</option>
                                </select>
-                               <input id="api_command" name="command" onkeydown="if (event.keyCode == 13) { oAPIHome.send_request(); }" /> <input class="api_button" type="button" name="apply" value="<%[ Send request ]%>" onclick="oAPIHome.send_request();" />
+                               <input id="api_command" class="w3-input w3-border w3-left" name="command" onkeydown="if (event.keyCode == 13) { oAPIHome.send_request(); }" style="width: 600px;" /> <a class="w3-button w3-green" type="button" name="apply" onclick="oAPIHome.send_request();"><i class="fas fa-paper-plane"></i> &nbsp;<%[ Send request ]%></a>
                        </div>
                        <div id="api_output">
                                <pre id="api_result"></pre>
                        </div>
                </div>
-               <div id="tab2">
-                       <input type="button" class="bbutton" onclick="$('#<%=$this->NewBasicClient->ClientID%>new_auth_client').slideToggle();" value="<%[ New HTTP Basic user ]%>" />
-                       <com:Application.Common.Portlets.NewAuthClient ID="NewBasicClient" AuthType="basic" OnCallback="loadBasicUsers" />
-                       <com:TActiveDataGrid
-                               ID="BasicClientList"
-                               AutoGenerateColumns="false"
-                               CellPadding="2"
-                               AlternatingItemStyle.BackColor="#4e4e50"
-                               CssClass="tab-grid"
-                       >
-                               <com:TBoundColumn
-                                       ID="BasicUsernameColumn"
-                                       HeaderText="<%[ Username ]%>"
-                                       DataField="username"
-                               />
-                               <com:TTemplateColumn
-                                       ID="BasicDeleteColumn"
-                                       HeaderText="<%[ Delete ]%>"
-                                       ItemStyle.HorizontalAlign="Center"
-                               >
-                               <prop:ItemTemplate>
-                                       <com:TActiveLinkButton
-                                               Text="<%[ Delete ]%>"
-                                               OnCommand="SourceTemplateControl.deleteBasicItem"
-                                               CommandParameter="<%#$this->getParent()->Data['username']%>"
-                                               Attributes.onclick="return confirm('<%[ Are you sure? ]%>')"
-                                               Visible="<%=($this->getParent()->getParent()->getItemCount() > 1)%>"
-                                       />
-                               </prop:ItemTemplate>
-                               </com:TTemplateColumn>
-                       </com:TActiveDataGrid>
-               </div>
-               <div id="tab3">
-                       <input type="button" class="bbutton" onclick="$('#<%=$this->NewOAuth2Client->ClientID%>new_auth_client').slideToggle();" value="<%[ New OAuth2 client ]%>" />
-                       <com:Application.Common.Portlets.NewAuthClient ID="NewOAuth2Client" AuthType="oauth2" OnCallback="loadOAuth2Users" />
-                       <com:TActiveDataGrid
-                               ID="OAuth2ClientList"
-                               AutoGenerateColumns="false"
-                               CellPadding="2"
-                               AlternatingItemStyle.BackColor="#4e4e50"
-                               CssClass="tab-grid"
-                       >
-                               <com:TBoundColumn
-                                       HeaderText="<%[ Name ]%>"
-                                       DataField="name"
-                               />
-                               <com:TBoundColumn
-                                       ID="ClientIDColumn"
-                                       HeaderText="<%[ Client ID ]%>"
-                                       DataField="client_id"
-                               />
-                               <com:TBoundColumn
-                                       HeaderText="<%[ Redirect URI ]%>"
-                                       DataField="redirect_uri"
-                               />
-                               <com:TTemplateColumn
-                                       ID="OAuth2DeleteColumn"
-                                       HeaderText="<%[ Delete ]%>"
-                                       ItemStyle.HorizontalAlign="Center"
-                               >
-                               <prop:ItemTemplate>
-                                       <com:TActiveLinkButton
-                                               Text="<%[ Edit ]%>"
-                                               OnCommand="SourceTemplateControl.editOAuth2Item"
-                                               CommandParameter="<%#$this->getParent()->Data['client_id']%>"
-                                       />
-                                       <com:TActiveLinkButton
-                                               Text="<%[ Delete ]%>"
-                                               OnCommand="SourceTemplateControl.deleteOAuth2Item"
-                                               CommandParameter="<%#$this->getParent()->Data['client_id']%>"
-                                               Attributes.onclick="if(!confirm('<%[ Are you sure? ]%>')) return false;"
-                                               Visible="<%=($this->getParent()->getParent()->getItemCount() > 1)%>"
-                                       />
-                               </prop:ItemTemplate>
-                               </com:TTemplateColumn>
-                       </com:TActiveDataGrid>
-               </div>
-               <com:TJuiDialog
-                       ID="APIOAuth2EditPopup"
-                       Options.Title="<%[ Edit OAuth2 client parameters ]%>"
-                       Options.AutoOpen="False"
-                       Options.Width="700px"
-               >
-                       <com:TPanel DefaultButton="APIOAuth2SaveBtn">
-                       <div class="line">
-                               <div class="text"><com:TLabel ForControl="APIOAuth2ClientId" Text="<%[ OAuth2 Client ID: ]%>" /></div>
-                               <div class="field">
-                                       <com:TActiveTextBox
-                                               ID="APIOAuth2ClientId"
-                                               CssClass="textbox"
-                                               ReadOnly="true"
-                                       />
-                               </div>
-                       </div>
-                       <div class="line">
-                               <div class="text"><com:TLabel ForControl="APIOAuth2ClientSecret" Text="<%[ OAuth2 Client Secret: ]%>" /></div>
-                               <div class="field">
-                                       <com:TActiveTextBox
-                                               ID="APIOAuth2ClientSecret"
-                                               CssClass="textbox"
-                                               CausesValidation="false"
-                                               MaxLength="50"
-                                       />
-                                       <com:TRequiredFieldValidator
-                                               CssClass="validator-block"
-                                               Display="Dynamic"
-                                               ControlCssClass="invalidate"
-                                               ControlToValidate="APIOAuth2ClientSecret"
-                                               ValidationGroup="APIOAuth2Edit"
-                                               Text="<%[ Please enter Client Secret. ]%>"
-                                       />
-                                       <com:TRegularExpressionValidator
-                                               CssClass="validator-block"
-                                               Display="Dynamic"
-                                               ControlCssClass="invalidate"
-                                               ControlToValidate="APIOAuth2ClientSecret"
-                                               RegularExpression="<%=OAuth2::CLIENT_SECRET_PATTERN%>"
-                                               ValidationGroup="APIOAuth2Edit"
-                                               Text="<%[ Invalid Client Secret value. Client Secret may contain any character that is not a whitespace character. ]%>"
-                                       />
-                                       <a href="javascript:void(0)" onclick="document.getElementById('<%=$this->APIOAuth2ClientSecret->ClientID%>').value = get_random_string('ABCDEFabcdef0123456789', 40); return false;"><%[ generate ]%></a>
-                               </div>
-                       </div>
-                       <div class="line">
-                               <div class="text"><com:TLabel ForControl="APIOAuth2RedirectURI" Text="<%[ OAuth2 Redirect URI (example: https://baculumgui:9095/web/redirect): ]%>" /></div>
-                               <div class="field">
-                                       <com:TActiveTextBox
-                                               ID="APIOAuth2RedirectURI"
-                                               CssClass="textbox"
-                                               CausesValidation="false"
-                                       />
-                                       <com:TRequiredFieldValidator
-                                               CssClass="validator-block"
-                                               Display="Dynamic"
-                                               ControlCssClass="invalidate"
-                                               ControlToValidate="APIOAuth2RedirectURI"
-                                               ValidationGroup="APIOAuth2Edit"
-                                               Text="<%[ Please enter Redirect URI. ]%>"
-                                       />
-                               </div>
-                       </div>
-                       <div class="line">
-                               <div class="text"><com:TLabel ForControl="APIOAuth2Scope" Text="<%[ OAuth2 scopes (space separated): ]%>" /></div>
-                               <div class="field">
-                                       <com:TActiveTextBox
-                                               ID="APIOAuth2Scope"
-                                               CssClass="textbox"
-                                               CausesValidation="false"
-                                               TextMode="MultiLine"
-                                       />
-                                       <a href="javascript:void(0)" onclick="set_scopes('<%=$this->APIOAuth2Scope->ClientID%>'); return false;" style="vertical-align: top"><%[ set all scopes ]%></a>
-                                       <com:TRequiredFieldValidator
-                                               CssClass="validator-block"
-                                               Display="Dynamic"
-                                               ControlCssClass="invalidate"
-                                               ControlToValidate="APIOAuth2Scope"
-                                               ValidationGroup="APIOAuth2Edit"
-                                               Text="<%[ Please enter OAuth2 scopes. ]%>"
-                                       />
-                               </div>
-                       </div>
-                       <div class="line">
-                               <div class="text"><com:TLabel ForControl="APIOAuth2BconsoleCfgPath" Text="<%[ Dedicated Bconsole config file path: ]%>" /></div>
-                               <div class="field">
-                                       <com:TActiveTextBox
-                                               ID="APIOAuth2BconsoleCfgPath"
-                                               CssClass="textbox"
-                                               CausesValidation="false"
-                                       /> <%[ (optional) ]%>
-                               </div>
-                       </div>
-                       <div class="line">
-                               <div class="text"><com:TLabel ForControl="APIOAuth2Name" Text="<%[ Short name: ]%>" /></div>
-                               <div class="field">
-                                       <com:TActiveTextBox
-                                               ID="APIOAuth2Name"
-                                               CssClass="textbox"
-                                               CausesValidation="false"
-                                       /> <%[ (optional) ]%>
-                               </div>
-                       </div>
-                       <div class="center">
-                               <com:BButton
-                                       Text="<%[ Cancel ]%>"
-                                       CausesValidation="false"
-                                       Attributes.onclick="$('#<%=$this->APIOAuth2EditPopup->ClientID%>').dialog('close'); return false;"
-                               />
-                               <com:BActiveButton
-                                       ID="APIOAuth2SaveBtn"
-                                       ValidationGroup="APIOAuth2Edit"
-                                       OnCommand="TemplateControl.saveOAuth2Item"
-                                       Text="<%[ Save ]%>"
-                               >
-                               </com:BActiveButton>
-                       </div>
-                       </com:TPanel>
-               </com:TJuiDialog>
        </div>
        <com:TActiveHiddenField ID="AuthParamsInput" />
        <com:TCallback ID="AuthParamsCallback" OnCallback="setAuthParams">
                        ids: {
                                section: 'section',
                                command: 'api_command',
+                               output: 'api_output',
                                result: 'api_result',
                                progress_bar: '<%=$this->Progress->ClientID%>',
                                progress_label: 'progress_label',
+                               progress_content: 'progress_content',
                                refresh_token: 'api_refresh',
                                auth_params_combo_container: 'auth_params_combo_container',
                                auth_params_combo: '<%=$this->AuthParamsCombo->ClientID%>',
                                auth_params_input: '<%=$this->AuthParamsInput->ClientID%>'
                        },
                        default_commands: {
-                               storages: '/api/v1/storages/',
-                               clients: '/api/v1/clients?limit=5',
-                               volumes: '/api/v1/volumes?limit=4',
-                               jobs: '/api/v1/jobs?limit=10',
-                               joblog: '/api/v1/joblog/1/',
-                               bvfs: '/api/v1/bvfs/lsdirs?jobids=1&path=&limit=8'
+                               storages: '/api/v2/storages/',
+                               clients: '/api/v2/clients?limit=5',
+                               volumes: '/api/v2/volumes?limit=4',
+                               jobs: '/api/v2/jobs?limit=10',
+                               joblog: '/api/v2/joblog/1/',
+                               bvfs: '/api/v2/bvfs/lsdirs?jobids=1&path=&limit=8'
                        },
                        token: null,
                        auth_params_cb: <%=$this->AuthParamsCallback->ActiveControl->Javascript%>,
                                this.set_events();
                                this.set_auth_params();
                                this.init_auth();
-                               this.init_tabs();
                                this.change_auth_params();
                        },
                        init_auth: function() {
                                var params = document.getElementById(this.ids.auth_params_input).value;
                                this.baculum_auth = JSON.parse(params);
                        },
-                       set_progress: function(value, text) {
+                       set_progress: function(value, content) {
                                $('#' + this.ids.progress_bar).progressbar('value', value);
-                               $('#' + this.ids.progress_label).text(text);
-                       },
-                       init_tabs: function() {
-                               $('#tabs').tabs({active: 0});
+                               $('#' + this.ids.progress_label).html(content);
                        },
                        init_basic: function() {
                                this.set_progress(3, '<%[ Basic auth ready... ]%>');
                                request.done(function(data) {
                                        if (typeof(data) == 'object' && data.hasOwnProperty('access_token')) {
                                                this.token = data.access_token;
-                                               this.set_progress(3, '<%[ Access token: ]%> ' + this.token);
+                                               var text = document.createTextNode('<%[ Access token: ]%> ');
+                                               var span_out = document.createElement('SPAN');
+                                               var span_in = document.createElement('SPAN');
+                                               span_in.id = this.ids.progress_content;
+                                               span_in.textContent = this.get_masked_token();
+                                               span_out.appendChild(text);
+                                               span_out.appendChild(span_in);
+                                               this.set_progress(3, span_out.outerHTML);
                                        }
                                }.bind(this));
                        },
                        },
                        send_request: function() {
                                var url = document.getElementById(this.ids.command).value;
+                               if (!url) {
+                                       return;
+                               }
                                var headers = {};
                                if (this.baculum_auth.auth_type == 'oauth2' && this.token) {
                                        headers = {'Authorization': 'Bearer ' + this.token};
                                }.bind(this));
                        },
                        show_result: function(data) {
+                               document.getElementById(this.ids.output).className = 'w3-code';
                                document.getElementById(this.ids.result).textContent = JSON.stringify(data, null, 2);
                        },
                        clear_result: function() {
+                               document.getElementById(this.ids.output).className = '';
                                document.getElementById(this.ids.result).textContent = '';
+                       },
+                       show_hide_token: function(el) {
+                               var span = document.getElementById(this.ids.progress_content);
+                               if (el.classList.contains('fa-eye')) {
+                                       span.textContent = this.token;
+                                       el.classList.remove('fa-eye');
+                                       el.classList.add('fa-eye-slash');
+                               } else {
+                                       span.textContent = this.get_masked_token();
+                                       el.classList.remove('fa-eye-slash');
+                                       el.classList.add('fa-eye');
+                               }
+                       },
+                       get_masked_token: function(token) {
+                               return this.token.substr(0, 7) + this.token.substr(8).replace(/./g, '*');
                        }
                };
                $(function() {
index 30f9aef3ea387ad46d8b67dd262020ae6a9c6338..d9d6844868a1e2de3250cdf6a1f89f7259d68a34 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
@@ -45,9 +45,8 @@ class APIHome extends BaculumAPIPage {
                        $this->goToPage('APIInstallWizard');
                        return;
                } elseif (!$this->IsCallback) {
-                       $this->loadBasicUsers(null, null);
-                       $this->loadOAuth2Users(null, null);
                        $this->setAuthParams(null, null);
+                       $this->loadAuthParams(null, null);
                }
        }
 
@@ -82,20 +81,6 @@ class APIHome extends BaculumAPIPage {
                $this->AuthParamsInput->Value = json_encode($params);
        }
 
-       public function loadBasicUsers($sender, $param) {
-               $basic_users = $this->getBasicUsers();
-               $this->BasicClientList->dataSource = $basic_users;
-               $this->BasicClientList->dataBind();
-               $this->BasicClientList->ensureChildControls();
-       }
-
-       public function loadOAuth2Users($sender, $param) {
-               $oauth2_cfg = $this->getModule('oauth2_config')->getConfig();
-               $this->OAuth2ClientList->dataSource = array_values($oauth2_cfg);
-               $this->OAuth2ClientList->dataBind();
-               $this->loadAuthParams(null, null);
-       }
-
        public function loadAuthParams($sender, $param) {
                $ids = $values = array();
                $config = $this->getModule('api_config')->getConfig();
@@ -114,67 +99,5 @@ class APIHome extends BaculumAPIPage {
                $this->AuthParamsCombo->dataBind();
        }
 
-       private function getBasicUsers() {
-               $basic_users = array();
-               $basic_cfg = $this->getModule('basic_apiuser')->getUsers();
-               foreach($basic_cfg as $user => $pwd) {
-                       $basic_users[] = array('username' => $user);
-               }
-               return $basic_users;
-       }
-
-       public function deleteBasicItem($sender, $param) {
-               $config = $this->getModule('basic_apiuser');
-               $config->removeUser($param->getCommandParameter());
-               $this->BasicClientList->DataSource = $this->getBasicUsers();
-               $this->BasicClientList->dataBind();
-       }
-
-       public function deleteOAuth2Item($sender, $param) {
-               $config = $this->getModule('oauth2_config');
-               $clients = $config->getConfig();
-               $client_id = $param->getCommandParameter();
-               if (key_exists($client_id, $clients)) {
-                       unset($clients[$client_id]);
-               }
-               $config->setConfig($clients);
-               $this->OAuth2ClientList->DataSource = array_values($config->getConfig());
-               $this->OAuth2ClientList->dataBind();
-               $this->loadAuthParams(null, null);
-       }
-
-       public function editOAuth2Item($sender, $param) {
-               $config = $this->getModule('oauth2_config');
-               $clients = $config->getConfig();
-               $client_id = $param->getCommandParameter();
-               if (key_exists($client_id, $clients)) {
-                       $this->APIOAuth2ClientId->Text = $clients[$client_id]['client_id'];
-                       $this->APIOAuth2ClientSecret->Text = $clients[$client_id]['client_secret'];
-                       $this->APIOAuth2RedirectURI->Text = $clients[$client_id]['redirect_uri'];
-                       $this->APIOAuth2Scope->Text = $clients[$client_id]['scope'];
-                       $this->APIOAuth2BconsoleCfgPath->Text = $clients[$client_id]['bconsole_cfg_path'];
-                       $this->APIOAuth2Name->Text = $clients[$client_id]['name'];
-               }
-               $this->APIOAuth2EditPopup->open();
-       }
-
-       public function saveOAuth2Item($sender, $param) {
-               $config = $this->getModule('oauth2_config');
-               $clients = $config->getConfig();
-               $client_id = $this->APIOAuth2ClientId->Text;
-               if (key_exists($client_id, $clients)) {
-                       $clients[$client_id]['client_id'] = $client_id;
-                       $clients[$client_id]['client_secret'] = $this->APIOAuth2ClientSecret->Text;
-                       $clients[$client_id]['redirect_uri'] = $this->APIOAuth2RedirectURI->Text;
-                       $clients[$client_id]['scope'] = $this->APIOAuth2Scope->Text;
-                       $clients[$client_id]['bconsole_cfg_path'] = $this->APIOAuth2BconsoleCfgPath->Text;
-                       $clients[$client_id]['name'] = $this->APIOAuth2Name->Text;
-                       $config->setConfig($clients);
-               }
-               $this->APIOAuth2EditPopup->close();
-               $this->OAuth2ClientList->DataSource = array_values($clients);
-               $this->OAuth2ClientList->dataBind();
-               $this->loadAuthParams(null, null);
-       }
 }
 ?>
index fd078c0260617f21ec13f92b073064a94204477a..8ef6fc3d4f0e86237f5ff37c75074b0e618a440e 100644 (file)
-<%@ MasterClass="Application.API.Layouts.Wizard" Theme="Baculum-v1"%>
+<%@ MasterClass="Application.API.Layouts.Wizard" Theme="Baculum-v2"%>
 <com:TContent ID="Wizard">
-       <com:BClientScript ScriptUrl=<%~ ../../JavaScript/misc.js %> />
        <com:TWizard ID="InstallWizard"
                CssClass="wizard"
-               StepStyle.CssClass="steps"
-               HeaderStyle.CssClass="header"
-               NavigationStyle.CssClass="navigation"
+               StepStyle.CssClass="w3-container"
+               HeaderStyle.CssClass="w3-container"
+               NavigationStyle.CssClass="w3-container"
                UseDefaultLayout="false"
                ShowSideBar="false"
                OnCancelButtonClick="wizardStop"
                OnCompleteButtonClick="wizardCompleted"
                >
                <prop:HeaderTemplate>
-                       <div>
-                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 0) ? 'first-active' : (($this->Parent->ActiveStepIndex === 1) ? 'first-next-active' : 'first')%>">
-                                       <div><com:TTranslate Text="Language" /></div>
-                               </div>
-                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 1) ? 'active' : (($this->Parent->ActiveStepIndex === 2) ? 'prev-active' : 'normal')%>">
-                                       <div><com:TTranslate Text="Catalog API" /></div>
+                       <div class="w3-half">
+                               <div class="w3-third w3-padding-16">
+                                       <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 0 ? 'w3-light-green' : 'w3-green')%>">
+                                               <div class="w3-left"><i class="fa fa-language w3-xxxlarge"></i></div>
+                                               <div class="w3-clear"></div>
+                                               <h4><com:TTranslate Text="Language" /></h4>
+                                       </div>
                                </div>
-                                <div class="step step-<%=($this->Parent->ActiveStepIndex === 2) ? 'active' : (($this->Parent->ActiveStepIndex === 3) ? 'prev-active' : 'normal')%>">
-                                       <div><com:TTranslate Text="Console API" /></div>
+                               <div class="w3-third w3-padding-16">
+                                       <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 1 ? 'w3-light-green' : 'w3-green')%>">
+                                               <div class="w3-left"><i class="fa fa-database w3-xxxlarge"></i></div>
+                                               <div class="w3-clear"></div>
+                                               <h4><com:TTranslate Text="Catalog" /></h4>
+                                       </div>
                                </div>
-                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 3) ? 'active' : (($this->Parent->ActiveStepIndex === 4) ? 'prev-active' : 'normal')%>">
-                                       <div><com:TTranslate Text="Config API" /></div>
+                               <div class="w3-third w3-padding-16">
+                                       <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 2 ? 'w3-light-green' : 'w3-green')%>">
+                                               <div class="w3-left"><i class="fa fa-terminal w3-xxxlarge"></i></div>
+                                               <div class="w3-clear"></div>
+                                               <h4><com:TTranslate Text="Console" /></h4>
+                                       </div>
                                </div>
-                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 4) ? 'active' : (($this->Parent->ActiveStepIndex === 5) ? 'prev-active' : 'normal')%>">
-                                       <div><com:TTranslate Text="Actions" /></div>
+                       </div>
+                       <div class="w3-half">
+                               <div class="w3-third w3-padding-16">
+                                       <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 3 ? 'w3-light-green' : 'w3-green')%>">
+                                               <div class="w3-left"><i class="fa fa-cog w3-xxxlarge"></i></div>
+                                               <div class="w3-clear"></div>
+                                               <h4><com:TTranslate Text="Config" /></h4>
+                                       </div>
                                </div>
-                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 5) ? 'active' : (($this->Parent->ActiveStepIndex === 6) ? 'prev-active' : 'normal')%>">
-                                       <div><com:TTranslate Text="Authentication" /></div>
+                               <div class="w3-third w3-padding-16">
+                                       <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 4 ? 'w3-light-green' : 'w3-green')%>">
+                                               <div class="w3-left"><i class="fa fa-key w3-xxxlarge"></i></div>
+                                               <div class="w3-clear"></div>
+                                               <h4><com:TTranslate Text="Authentication" /></h4>
+                                       </div>
                                </div>
-                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 6) ? 'last-active' : (($this->Parent->ActiveStepIndex === 7) ? 'last-prev-active' : 'last')%>">
-                                       <div><com:TTranslate Text="Finish" /></div>
+                               <div class="w3-third w3-padding-16">
+                                       <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 5 ? 'w3-light-green' : 'w3-green')%>">
+                                               <div class="w3-left"><i class="fa fa-check-square w3-xxxlarge"></i></div>
+                                               <div class="w3-clear"></div>
+                                               <h4><com:TTranslate Text="Finish" /></h4>
+                                       </div>
                                </div>
-                               <div id="title"><%=$this->Parent->ActiveStep->Title%><div>
+                       </div>
+                       <div class="w3-clear"></div>
+                       <div class="w3-green w3-margin-right w3-margin-bottom w3-padding-small">
+                               <h3><%=$this->Parent->ActiveStep->Title%></h3>
                        </div>
                </prop:HeaderTemplate>
                <prop:StartNavigationTemplate>
-                               <com:TPanel CssClass="button-cancel" Visible="<%=$this->getPage()->first_run === false%>"><com:BButton CommandName="Cancel" Text="<%[ Cancel ]%>" /></com:TPanel>
-                               <div <%=$this->getPage()->first_run === false ? 'class="button-prev-next"' : ''%>><com:BButton CommandName="NextStep" Text="<%[ Next &raquo; ]%>" /></div>
+                       <div class="w3-center w3-section">
+                               <com:TLinkButton
+                                       CommandName="Cancel"
+                                       CssClass="w3-button w3-red"
+                                       Visible="<%=$this->getPage()->first_run === false%>"
+                               >
+                                       <i class="fa fa-times"></i> &nbsp;<%[ Cancel ]%>
+                               </com:TLinkButton>
+                               <com:TLinkButton
+                                       CommandName="NextStep"
+                                       CssClass="w3-button w3-green"
+                               >
+                                       <%[ Next ]%>&nbsp; <i class="fa fa-angle-right"></i>
+                               </com:TLinkButton>
+                       </div>
                </prop:StartNavigationTemplate>
 
                <prop:StepNavigationTemplate>
-                       <com:TPanel CssClass="button-cancel" Visible="<%=$this->getPage()->first_run === false%>"><com:BButton CommandName="Cancel" Text="<%[ Cancel ]%>" /></com:TPanel>
-                       <div <%=$this->getPage()->first_run === false ? 'class="button-prev-next"' : ''%>>
-                               <com:BButton CausesValidation="False" CssClass="bbutton" CommandName="PreviousStep" Text="<%[ &laquo; Previous ]%>" />
-                               <com:BButton ID="NextButton" CommandName="NextStep" Attributes.onclick="return (typeof(wizard_validation) == 'function') ? wizard_validation() : true;" Text="<%[ Next &raquo; ]%>" />
+                       <div class="w3-center w3-section">
+                               <com:TLinkButton
+                                       CommandName="Cancel"
+                                       CssClass="w3-button w3-red"
+                                       Visible="<%=$this->getPage()->first_run === false%>"
+                               >
+                                       <i class="fa fa-times"></i> &nbsp;<%[ Cancel ]%>
+                               </com:TLinkButton>
+                               <com:TLinkButton
+                                       ID="PreviousStepBtn"
+                                       CommandName="PreviousStep"
+                                       CausesValidation="false"
+                                       CssClass="w3-button w3-green"
+                               >
+                                       <i class="fa fa-angle-left"></i> &nbsp;<%[ Previous ]%>
+                               </com:TLinkButton>
+                               <com:TLinkButton
+                                       ID="NextStepBtn"
+                                       CommandName="NextStep"
+                                       CssClass="w3-button w3-green"
+                                       Attributes.onclick="return (typeof(wizard_validation) == 'function') ? wizard_validation() : true;"
+                               >
+                                       <%[ Next ]%>&nbsp; <i class="fa fa-angle-right"></i>
+                               </com:TLinkButton>
                        </div>
                </prop:StepNavigationTemplate>
+
                <prop:FinishNavigationTemplate>
-                       <com:TPanel CssClass="button-cancel" Visible="<%=$this->getPage()->first_run === false%>"><com:BButton CommandName="Cancel" Text="<%[ Cancel ]%>" /></com:TPanel>
-                       <div <%=$this->getPage()->first_run === false ? 'class="button-prev-next"' : ''%>>
-                               <com:BButton CausesValidation="False" CssClass="bbutton" CommandName="PreviousStep" Text="<%[ &laquo; Previous ]%>" />
-                               <com:BButton CommandName="Complete" Text="<%[ Save ]%>" />
+                       <div class="w3-center w3-section">
+                               <com:TLinkButton
+                                       CommandName="Cancel"
+                                       CssClass="w3-button w3-red"
+                                       Visible="<%=$this->getPage()->first_run === false%>"
+                               >
+                                       <i class="fa fa-times"></i> &nbsp;<%[ Cancel ]%>
+                               </com:TLinkButton>
+                               <com:TLinkButton
+                                       CommandName="PreviousStep"
+                                       CausesValidation="false"
+                                       CssClass="w3-button w3-green"
+                               >
+                                       <i class="fa fa-angle-left"></i> &nbsp;<%[ Previous ]%>
+                               </com:TLinkButton>
+                               <com:TLinkButton
+                                       CommandName="Complete"
+                                       CssClass="w3-button w3-green"
+                               >
+                                       <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TLinkButton>
                        </div>
                </prop:FinishNavigationTemplate>
                <com:TWizardStep ID="Step1" Title="<%[ Step 1 - select language ]%>" StepType="Auto">
-                       <div class="line">
-                               <div class="text"><com:TLabel ForControl="Lang" Text="<%[ Language: ]%>" /></div>
-                               <div class="field">
-                                       <com:TActiveDropDownList ID="Lang" CssClass="textbox" Width="150px" OnTextChanged="setLang" CausesValidation="false" ClientSide.OnComplete="location.reload();">
+                       <div class="w3-container">
+                               <div class="w3-third"><com:TLabel ForControl="Lang" Text="<%[ Language: ]%>" /></div>
+                               <div class="w3-third">
+                                       <com:TActiveDropDownList
+                                               ID="Lang"
+                                               CssClass="w3-select w3-border"
+                                               Width="150px"
+                                               OnTextChanged="setLang"
+                                               CausesValidation="false"
+                                               ClientSide.OnComplete="location.reload();"
+                                       >
                                                <com:TListItem Value="en" Text="English" />
                                                <com:TListItem Value="pl" Text="Polish" />
                                                <com:TListItem Value="pt" Text="Portuguese" />
                </com:TWizardStep>
                <com:TWizardStep ID="Step2" Title="<%[ Step 2 - share the Bacula Catalog Database ]%>" StepType="Auto">
                        <p><%[ Do you want to setup and to share the Bacula Catalog Database access for this API instance? ]%></p>
-                       <div class="line left">
-                               <com:TRadioButton ID="DatabaseNo" GroupName="SelectDatabase" Attributes.onclick="$('#configure_database').hide();" /> <com:TLabel ForControl="DatabaseNo" Text="<%[ No ]%>" />
+                       <div class="w3-container w3-section">
+                               <com:TRadioButton
+                                       ID="DatabaseNo"
+                                       CssClass="w3-radio"
+                                       GroupName="SelectDatabase"
+                                       Attributes.onclick="$('#configure_database').hide();"
+                               /> <com:TLabel ForControl="DatabaseNo" Text="<%[ No ]%>" />
                        </div>
-                       <div class="line left">
-                               <com:TRadioButton ID="DatabaseYes" GroupName="SelectDatabase" Attributes.onclick="$('#configure_database').show();" /> <com:TLabel ForControl="DatabaseYes" Text="<%[ Yes ]%>" />
+                       <div class="w3-container w3-section">
+                               <com:TRadioButton
+                                       ID="DatabaseYes"
+                                       CssClass="w3-radio"
+                                       GroupName="SelectDatabase"
+                                       Attributes.onclick="$('#configure_database').show();"
+                               /> <com:TLabel ForControl="DatabaseYes" Text="<%[ Yes ]%>" />
                        </div>
                        <div id="configure_database" style="display: <%=($this->DatabaseYes->Checked === true) ? '' : 'none';%>">
                                <com:TActivePanel ID="Step2Content">
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="DBType" Text="<%[ Database type: ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="DBType" Text="<%[ Database type: ]%>" /></div>
+                                               <div class="w3-col w3-third">
                                                        <com:TActiveDropDownList
                                                                ID="DBType"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-select w3-border"
                                                                Width="170px"
                                                                OnLoad="setDBType"
                                                                OnTextChanged="setDBType"
                                                                <com:TListItem Value="sqlite" Text="SQLite" />
                                                        </com:TActiveDropDownList>
                                                        <com:TCompareValidator
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
                                                                ControlToValidate="DBType"
                                                                DataType="String"
                                                        />
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="DBName" Text="<%[ Database name: ]%>" /></div>
-                                               <div class="field">
-                                                       <com:TTextBox ID="DBName" CssClass="textbox" />
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="DBName" Text="<%[ Database name: ]%>" /></div>
+                                               <div class="w3-col w3-third">
+                                                       <com:TTextBox ID="DBName" CssClass="w3-input w3-border" />
                                                        <com:TRequiredFieldValidator
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
                                                                ControlCssClass="invalidate"
                                                                ControlToValidate="DBName"
                                                        />
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="Login" Text="<%[ Login: ]%>" /></div>
-                                               <div class="field">
-                                                       <com:TActiveTextBox ID="Login" CssClass="textbox" />
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="Login" Text="<%[ Login: ]%>" /></div>
+                                               <div class="w3-col w3-third">
+                                                       <com:TActiveTextBox ID="Login" CssClass="w3-input w3-border" />
                                                        <com:TRequiredFieldValidator
                                                                ID="LoginValidator"
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
                                                                ControlCssClass="invalidate"
                                                                ControlToValidate="Login"
                                                        />
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="Password" Text="<%[ Password: ]%>" /></div>
-                                               <div class="field"><com:TActiveTextBox
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="Password" Text="<%[ Password: ]%>" /></div>
+                                               <div class="w3-col w3-third"><com:TActiveTextBox
                                                        ID="Password"
-                                                       CssClass="textbox"
+                                                       CssClass="w3-input w3-border"
                                                        TextMode="Password"
                                                        PersistPassword="true"
                                                 /></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="IP" Text="<%[ IP address (or hostname): ]%>" /></div>
-                                               <div class="field">
-                                                       <com:TActiveTextBox ID="IP" CssClass="textbox" />
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="IP" Text="<%[ IP address (or hostname): ]%>" /></div>
+                                               <div class="w3-col w3-third">
+                                                       <com:TActiveTextBox ID="IP" CssClass="w3-input w3-border" />
                                                        <com:TRequiredFieldValidator
                                                                ID="IPValidator"
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="IP"
                                                                ValidationGroup="DbGroup"
                                                                Text="<%[ Please enter IP address or hostname. ]%>"
                                                        />
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="Port" Text="<%[ Port: ]%>" /></div>
-                                               <div class="field">
-                                                       <com:TActiveTextBox ID="Port" CssClass="textbox" Width="55px" MaxLength="5" Enabled="false" />
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="Port" Text="<%[ Port: ]%>" /></div>
+                                               <div class="w3-col w3-third">
+                                                       <com:TActiveTextBox ID="Port" CssClass="w3-input w3-border" Width="55px" MaxLength="5" Enabled="false" />
                                                        <com:TRequiredFieldValidator
                                                                ID="PortValidator"
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="Port"
                                                                ValidationGroup="DbGroup"
                                                                Text="<%[ Please enter database port. ]%>"
                                        </div>
                                        <com:TActivePanel
                                                ID="DBPathField"
-                                               CssClass="line"
+                                               CssClass="w3-row w3-section"
                                                Display="<%=$this->getPage()->first_run === false && !$this->IsCallBack && $this->getPage()->config['db']['type'] == 'sqlite' ? 'Fixed' : $this->DBPathField->Display%>">
-                                               <div class="text"><com:TLabel ForControl="DBPath" Text="<%[ SQLite database path: ]%>" /></div>
-                                               <div class="field">
-                                                       <com:TActiveTextBox ID="DBPath" CssClass="textbox" Enabled="false" />
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="DBPath" Text="<%[ SQLite database path: ]%>" /></div>
+                                               <div class="w3-col w3-third">
+                                                       <com:TActiveTextBox ID="DBPath" CssClass="w3-input w3-border" Enabled="false" />
                                                        <com:TRequiredFieldValidator
                                                                ID="DBPathValidator"
                                                                Display="Dynamic"
-                                                               CssClass="validator-block"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="DBPath"
                                                                ValidationGroup="DbGroup"
                                                                Text="<%[ Please enter database path. ]%>"
                                                        />
                                                </div>
                                        </com:TActivePanel>
-                                       <div class="line">
-                                               <div class="text" style="vertical-align: top"><com:TLabel ForControl="ConnectionTest" Text="<%[ Connection test: ]%>" CssClass="test_label" /></div>
-                                               <div class="field">
-                                                       <table border="0" cellpadding="1px">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third" style="vertical-align: top"><com:TLabel ForControl="ConnectionTest" Text="<%[ Connection test: ]%>" CssClass="test_label" /></div>
+                                               <div class="w3-col w3-third">
+                                                       <table>
                                                                <tr>
                                                                        <td align="center" valign="middle">
-                                                                               <com:TActiveButton ID="ConnectionTest" Text="<%[ test ]%>" CausesValidation="false" OnCallback="connectionDBTest">
+                                                                               <com:TActiveLinkButton
+                                                                                       ID="ConnectionTest"
+                                                                                       CssClass="w3-button w3-green"
+                                                                                       CausesValidation="false"
+                                                                                       OnCallback="connectionDBTest"
+                                                                               >
                                                                                        <prop:ClientSide.OnLoading>
-                                                                                               $('#<%=$this->DbTestResultOk->ClientID%>').hide();
+                                                                                               $('#db_test_result_ok').hide();
+                                                                                               $('#db_test_result_err').hide();
                                                                                                $('#<%=$this->DbTestResultErr->ClientID%>').hide();
-                                                                                               $('#<%=$this->DbTestLoader->ClientID%>').show();
+                                                                                               $('#db_test_loader').show();
                                                                                        </prop:ClientSide.OnLoading>
                                                                                        <prop:ClientSide.OnComplete>
-                                                                                               $('#<%=$this->DbTestLoader->ClientID%>').hide();
+                                                                                               $('#db_test_loader').hide();
                                                                                        </prop:ClientSide.OnComplete>
-                                                                               </com:TActiveButton>
+                                                                                       <i class="fas fa-play"></i> &nbsp;<%[ test ]%>
+                                                                               </com:TActiveLinkButton>
                                                                        </td>
-                                                                       <td align="center" valign="middle">
-                                                                               <com:TActiveLabel ID="DbTestLoader" Display="None"><img src="<%=$this->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></com:TActiveLabel>
-                                                                               <com:TActiveLabel ID="DbTestResultOk" Display="None" CssClass="validate"><img src="<%=$this->getTheme()->getBaseUrl()%>/icon_ok.png" alt="Validate" /> <%[ OK ]%></com:TActiveLabel>
-                                                                               <com:TActiveLabel ID="DbTestResultErr" Display="None" CssClass="validator-block left"><img src="<%=$this->getTheme()->getBaseUrl()%>/icon_err.png" alt="Invalidate" /> <%[ Connection error ]%></com:TActiveLabel>
+                                                                       <td style="padding-left: 10px">
+                                                                               <span id="db_test_loader" style="display: none">
+                                                                                       <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                                               </span>
+                                                                               <span id="db_test_result_ok" class="w3-text-green" style="display: none">
+                                                                                       <i class="fas fa-check"></i> &nbsp;<%[ OK ]%>
+                                                                               </span>
+                                                                               <span id="db_test_result_err" class="w3-text-red" style="display: none">
+                                                                                       <i class="fas fa-exclamation-circle"></i> &nbsp;
+                                                                               </span>
+                                                                               <com:TActiveLabel ID="DbTestResultErr" CssClass="w3-text-red" Display="None"><%[ Connection error ]%></com:TActiveLabel>
                                                                        </td>
                                                                </tr>
                                                        </table>
                                                </div>
                                        </div>
-                                       <div style="clear: left"></div>
                                </com:TActivePanel>
                        </div>
                        <script type="text/javascript">
                </com:TWizardStep>
                <com:TWizardStep ID="Step3" Title="<%[ Step 3 - share the Bacula Bconsole commands interface ]%>" StepType="Auto">
                        <p><%[ Do you want to setup and share the Bacula Bconsole interface to execute commands in this API instance? ]%></p>
-                       <div class="line left">
-                               <com:TRadioButton ID="ConsoleNo" GroupName="SelectConsole" Attributes.onclick="$('#configure_console').hide();" /> <com:TLabel ForControl="ConsoleNo" Text="<%[ No ]%>" />
+                       <div class="w3-container w3-section">
+                               <com:TRadioButton
+                                       ID="ConsoleNo"
+                                       GroupName="SelectConsole"
+                                       CssClass="w3-radio"
+                                       Attributes.onclick="$('#configure_console').hide();"
+                               /> <com:TLabel ForControl="ConsoleNo" Text="<%[ No ]%>" />
                        </div>
-                       <div class="line left">
-                               <com:TRadioButton ID="ConsoleYes" GroupName="SelectConsole" Attributes.onclick="$('#configure_console').show();" /> <com:TLabel ForControl="ConsoleYes" Text="<%[ Yes ]%>" />
+                       <div class="w3-container w3-section">
+                               <com:TRadioButton
+                                       ID="ConsoleYes"
+                                       GroupName="SelectConsole"
+                                       CssClass="w3-radio"
+                                       Attributes.onclick="$('#configure_console').show();"
+                               /> <com:TLabel ForControl="ConsoleYes" Text="<%[ Yes ]%>" />
                        </div>
                        <div id="configure_console" style="display: <%=($this->ConsoleYes->Checked === true) ? '' : 'none';%>">
-                               <div class="line">
-                                       <div class="text"><com:TLabel ForControl="BconsolePath" Text="<%[ Bconsole binary file path: ]%>" /></div>
-                                       <div class="field">
-                                               <com:TTextBox ID="BconsolePath" CssClass="textbox" CausesValidation="false" />
-                                               <com:TRequiredFieldValidator CssClass="validator-block" Display="Dynamic" ControlCssClass="invalidate" ControlToValidate="BconsolePath" Text="<%[ Please enter bconsole path. ]%>" />
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third">
+                                               <com:TLabel
+                                                       ForControl="BconsolePath"
+                                                       Text="<%[ Bconsole binary file path: ]%>" />
+                                       </div>
+                                       <div class="w3-col w3-third">
+                                               <com:TTextBox
+                                                       ID="BconsolePath"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                               />
+                                               <com:TRequiredFieldValidator
+                                                       Display="Dynamic"
+                                                       ControlToValidate="BconsolePath"
+                                                       Text="<%[ Please enter bconsole path. ]%>"
+                                               />
                                        </div>
                                </div>
-                               <div class="line">
-                                       <div class="text"><com:TLabel ForControl="BconsoleConfigPath" Text="<%[ Bconsole admin config file path: ]%>" /></div>
-                                       <div class="field">
-                                               <com:TTextBox ID="BconsoleConfigPath" CssClass="textbox" CausesValidation="false" />
-                                               <com:TRequiredFieldValidator CssClass="validator-block" Display="Dynamic" ControlCssClass="invalidate" ControlToValidate="BconsoleConfigPath" Text="<%[ Please enter bconsole config file path. ]%>" />
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third">
+                                               <com:TLabel
+                                                       ForControl="BconsoleConfigPath"
+                                                       Text="<%[ Bconsole admin config file path: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-third">
+                                               <com:TTextBox
+                                                       ID="BconsoleConfigPath"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                               />
+                                               <com:TRequiredFieldValidator
+                                                       Display="Dynamic"
+                                                       ControlToValidate="BconsoleConfigPath"
+                                                       Text="<%[ Please enter bconsole config file path. ]%>"
+                                               />
                                        </div>
                                </div>
-                               <div class="line">
-                                       <div class="text"><com:TLabel ForControl="UseSudo" Text="<%[ Use sudo: ]%>" /></div>
-                                       <div class="field">
-                                               <com:TCheckBox ID="UseSudo" /> <a href="javascript:void(0)" onclick="get_sudo_config('bconsole');"><%[ Get sudo configuration ]%></a>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third">
+                                               <com:TLabel
+                                                       ForControl="UseSudo"
+                                                       Text="<%[ Use sudo: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-third">
+                                               <com:TCheckBox
+                                                       ID="UseSudo"
+                                                       CssClass="w3-check"
+                                               /> &nbsp;<a href="javascript:void(0)" onclick="get_sudo_config('bconsole');"><%[ Get sudo configuration ]%></a>
 
                                        </div>
                                </div>
-                               <div class="line">
-                                       <div class="text" style="vertical-align: top"><com:TLabel ForControl="BconsoleConnectionTest" Text="<%[ Bconsole connection test: ]%>" CssClass="test_label" /></div>
-                                       <div class="field">
-                                               <table border="0" cellpadding="1px">
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third">
+                                               <com:TLabel
+                                                       ForControl="BconsoleConnectionTest"
+                                                       Text="<%[ Bconsole connection test: ]%>"
+                                               /></div>
+                                       <div class="w3-col w3-third">
+                                               <table>
                                                        <tr>
-                                                               <td  align="center" valign="middle">
-                                                                       <com:TActiveButton ID="BconsoleConnectionTest" Text="<%[ test ]%>" CausesValidation="false" OnCallback="connectionBconsoleTest">
+                                                               <td>
+                                                                       <com:TActiveLinkButton
+                                                                               ID="BconsoleConnectionTest"
+                                                                               CssClass="w3-button w3-green"
+                                                                               CausesValidation="false"
+                                                                               OnCallback="connectionBconsoleTest"
+                                                                       >
                                                                                <prop:ClientSide.OnLoading>
-                                                                                       $('#<%=$this->BconsoleTestResultOk->ClientID%>').hide();
+                                                                                       $('#bconsole_test_result_ok').hide();
+                                                                                       $('#bconsole_test_result_err').hide();
                                                                                        $('#<%=$this->BconsoleTestResultErr->ClientID%>').hide();
-                                                                                       $('#<%=$this->BconsoleTestLoader->ClientID%>').show();
+                                                                                       $('#bconsole_test_loader').show();
                                                                                </prop:ClientSide.OnLoading>
                                                                                <prop:ClientSide.OnComplete>
-                                                                                       $('#<%=$this->BconsoleTestLoader->ClientID%>').hide();
+                                                                                       $('#bconsole_test_loader').hide();
                                                                                </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
+                                                                               <i class="fas fa-play"></i> &nbsp;<%[ test ]%>
+                                                                       </com:TActiveLinkButton>
                                                                </td>
-                                                               <td align="center" valign="middle">
-                                                                       <com:TActiveLabel ID="BconsoleTestLoader" Display="None"><img src="<%=$this->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></com:TActiveLabel>
-                                                                       <com:TActiveLabel ID="BconsoleTestResultOk" Display="None" CssClass="validate" EnableViewState="false"><img src="<%=$this->getTheme()->getBaseUrl()%>/icon_ok.png" alt="Validate" /> <%[ OK ]%></com:TActiveLabel>
-                                                                       <com:TActiveLabel ID="BconsoleTestResultErr" Display="None" CssClass="validator-block left" EnableViewState="false"><img src="<%=$this->getTheme()->getBaseUrl()%>/icon_err.png" alt="Invalidate" /> <%[ Connection error ]%></com:TActiveLabel>
+                                                               <td style="padding-left: 10px">
+                                                                       <span id="bconsole_test_loader" style="display: none">
+                                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                                       </span>
+                                                                       <span id="bconsole_test_result_ok" class="w3-text-green" style="display: none">
+                                                                               <i class="fas fa-check"></i> &nbsp;<%[ OK ]%>
+                                                                       </span>
+                                                                       <span id="bconsole_test_result_err" class="w3-text-red" style="display: none">
+                                                                               <i class="fas fa-exclamation-circle"></i> &nbsp;
+                                                                       </span>
+                                                                       <com:TActiveLabel ID="BconsoleTestResultErr" CssClass="w3-text-red" Display="None"><%[ Connection error ]%></com:TActiveLabel>
                                                                </td>
                                                        </tr>
                                                </table>
                </com:TWizardStep>
                <com:TWizardStep ID="Step4" Title="<%[ Step 4 - share the Bacula configuration interface ]%>" StepType="Auto">
                        <p><%[ Do you want to setup and share the Bacula configuration interface to configure Bacula components via this API instance? ]%></p>
-                       <div class="line left">
-                               <com:TRadioButton ID="ConfigNo" GroupName="SelectConfig" Attributes.onclick="$('#configure_config').hide();" /> <com:TLabel ForControl="ConfigNo" Text="<%[ No ]%>" />
+                       <div class="w3-container w3-section">
+                               <com:TRadioButton
+                                       ID="ConfigNo"
+                                       GroupName="SelectConfig"
+                                       CssClass="w3-radio"
+                                       Attributes.onclick="$('#configure_config').hide();"
+                               /> <com:TLabel ForControl="ConfigNo" Text="<%[ No ]%>" />
                        </div>
-                       <div class="line left">
-                               <com:TRadioButton ID="ConfigYes" GroupName="SelectConfig" Attributes.onclick="$('#configure_config').show();" /> <com:TLabel ForControl="ConfigYes" Text="<%[ Yes ]%>" />
+                       <div class="w3-container w3-section">
+                               <com:TRadioButton
+                                       ID="ConfigYes"
+                                       GroupName="SelectConfig"
+                                       CssClass="w3-radio"
+                                       Attributes.onclick="$('#configure_config').show();"
+                               /> <com:TLabel ForControl="ConfigYes" Text="<%[ Yes ]%>" />
                        </div>
                        <div id="configure_config" style="display: <%=($this->ConfigYes->Checked === true) ? 'block' : 'none';%>">
-                               <div id="config_fields">
-                                       <fieldset class="config_field">
-                                               <legend><%[ General configuration ]%></legend>
-                                               <div class="config_lines">
-                                                       <div class="line left" title="<%[ In this directory Baculum API saves temporarily Bacula configuration files (mainly for validation purposes) just before they are written as real Bacula configuration files. ]%>">
-                                                               <div class="text"><com:TLabel ForControl="BConfigDir" Text="<%[ Baculum working directory for Bacula config: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TRequiredFieldValidator ValidationGroup="ConfigGroup" ControlToValidate="BConfigDir" Display="Dynamic" Text="<%[ Directory path field is required. ]%>" />
-                                                                       <com:TActiveCustomValidator ID="BConfigDirWritableTest" ValidationGroup="ConfigGroup" ControlToValidate="BConfigDir" Text="<%[ Provided directory path is not writable by web server. ]%>" ControlCssClass="validation-error" Display="Dynamic" OnServerValidate="testConfigDir" CssClass="validator" />
-                                                                       <com:TTextBox ID="BConfigDir" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="BJSONUseSudo" Text="<%[ Use sudo: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCheckBox ID="BJSONUseSudo" /> <a href="javascript:void(0)" onclick="get_sudo_config('config');"><%[ Get sudo configuration ]%></a>
-
-                                                               </div>
-                                                       </div>
+                               <fieldset>
+                                       <legend><%[ General configuration ]%></legend>
+                                       <div class="w3-row w3-section" title="<%[ In this directory Baculum API saves temporarily Bacula configuration files (mainly for validation purposes) just before they are written as real Bacula configuration files. ]%>">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="BConfigDir"
+                                                               Text="<%[ Baculum working directory for Bacula config: ]%>"
+                                                       />
                                                </div>
-                                               <span class="config_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                               <com:TActiveLabel ID="BConfigDirTestOk" Display="None" CssClass="validate config_test_result" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></com:TActiveLabel>
-                                               <com:TActiveLabel ID="BConfigDirTestErr" Display="None" CssClass="validator left" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></com:TActiveLabel>
-                                       </fieldset>
-                                       <fieldset class="config_field">
-                                               <legend>Director</legend>
-                                               <div class="config_lines">
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="BDirJSONPath" Text="<%[ bdirjson binary file path: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCustomValidator ValidationGroup="ConfigGroup" ControlToValidate="BDirJSONPath" ClientValidationFunction="bjsontools_validator" Display="Dynamic" Text="<%[ There is required to provide both binary file and config file paths. ]%>" />
-                                                                       <com:TTextBox ID="BDirJSONPath" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="DirCfgPath" Text="<%[ Main Director config file path (usually bacula-dir.conf): ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCustomValidator ValidationGroup="ConfigGroup" ControlToValidate="DirCfgPath" ClientValidationFunction="bjsontools_validator" Display="Dynamic" Text="<%[ There is required to provide both binary file and config file paths. ]%>" />
-                                                                       <com:TTextBox ID="DirCfgPath" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TTextBox
+                                                               ID="BConfigDir"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       /> &nbsp;
+                                                       <span class="config_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <com:TActiveLabel
+                                                               ID="BConfigDirTestOk"
+                                                               Display="None"
+                                                               CssClass="w3-text-green"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </com:TActiveLabel>
+                                                       <com:TActiveLabel
+                                                               ID="BConfigDirTestErr"
+                                                               Display="None"
+                                                               CssClass="w3-text-red"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <!--i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i--> &nbsp;<%[ Error ]%>
+                                                       </com:TActiveLabel>
+                                                       <com:TRequiredFieldValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="BConfigDir"
+                                                               Display="Dynamic"
+                                                               Text="<%[ Directory path field is required. ]%>"
+                                                       />
+                                                       <com:TActiveCustomValidator
+                                                               ID="BConfigDirWritableTest"
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="BConfigDir"
+                                                               Text="<%[ Provided directory path is not writable by web server. ]%>"
+                                                               Display="Dynamic"
+                                                               OnServerValidate="testConfigDir"
+                                                       />
                                                </div>
-                                               <span class="config_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                               <com:TActiveLabel ID="BDirJSONPathTestOk" Display="None" CssClass="validate config_test_result" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></com:TActiveLabel>
-                                               <com:TActiveLabel ID="BDirJSONPathTestErr" Display="None" CssClass="validator left" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Connection error ]%>" /> <%[ Connection error ]%></com:TActiveLabel>
-                                       </fieldset>
-                                       <fieldset class="config_field">
-                                               <legend>Storage Daemon</legend>
-                                               <div class="config_lines">
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="BSdJSONPath" Text="<%[ bsdjson binary file path: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCustomValidator ValidationGroup="ConfigGroup" ControlToValidate="BSdJSONPath" ClientValidationFunction="bjsontools_validator" Display="Dynamic" Text="<%[ There is required to provide both binary file and config file paths. ]%>" />
-                                                                       <com:TTextBox ID="BSdJSONPath" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="SdCfgPath" Text="<%[ Main Storage Daemon config file path (usually bacula-sd.conf): ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCustomValidator ValidationGroup="ConfigGroup" ControlToValidate="SdCfgPath" ClientValidationFunction="bjsontools_validator" Display="Dynamic" Text="<%[ There is required to provide both binary file and config file paths. ]%>" />
-                                                                       <com:TTextBox ID="SdCfgPath" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="BJSONUseSudo"
+                                                               Text="<%[ Use sudo: ]%>"
+                                                       />
                                                </div>
-                                               <span class="config_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                               <com:TActiveLabel ID="BSdJSONPathTestOk" Display="None" CssClass="validate config_test_result" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></com:TActiveLabel>
-                                               <com:TActiveLabel ID="BSdJSONPathTestErr" Display="None" CssClass="validator left" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Connection error ]%>" /> <%[ Connection error ]%></com:TActiveLabel>
-                                       </fieldset>
-                                       <fieldset class="config_field">
-                                               <legend>File Daemon/Client</legend>
-                                               <div class="config_lines">
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="BFdJSONPath" Text="<%[ bfdjson binary file path: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCustomValidator ValidationGroup="ConfigGroup" ControlToValidate="BFdJSONPath" ClientValidationFunction="bjsontools_validator" Display="Dynamic" Text="<%[ There is required to provide both binary file and config file paths. ]%>" />
-                                                                       <com:TTextBox ID="BFdJSONPath" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="FdCfgPath" Text="<%[ Main File Daemon config file path (usually bacula-fd.conf): ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCustomValidator ValidationGroup="ConfigGroup" ControlToValidate="FdCfgPath" ClientValidationFunction="bjsontools_validator" Display="Dynamic" Text="<%[ There is required to provide both binary file and config file paths. ]%>" />
-                                                                       <com:TTextBox ID="FdCfgPath" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TCheckBox
+                                                               ID="BJSONUseSudo"
+                                                               CssClass="w3-check"
+                                                       /> &nbsp;<a href="javascript:void(0)" onclick="get_sudo_config('config');"><%[ Get sudo configuration ]%></a>
+                                               </div>
+                                       </div>
+                               </fieldset>
+                               <fieldset>
+                                       <legend>Director</legend>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="BDirJSONPath"
+                                                               Text="<%[ bdirjson binary file path: ]%>"
+                                                       />
                                                </div>
-                                               <span class="config_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                               <com:TActiveLabel ID="BFdJSONPathTestOk" Display="None" CssClass="validate config_test_result" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></com:TActiveLabel>
-                                               <com:TActiveLabel ID="BFdJSONPathTestErr" Display="None" CssClass="validator left" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Connection error ]%>" /> <%[ Connection error ]%></com:TActiveLabel>
-                                       </fieldset>
-                                       <fieldset class="config_field">
-                                               <legend>Bconsole</legend>
-                                               <div class="config_lines">
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="BBconsJSONPath" Text="<%[ bbconsjson binary file path: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCustomValidator ValidationGroup="ConfigGroup" ControlToValidate="BBconsJSONPath" ClientValidationFunction="bjsontools_validator" Display="Dynamic" Text="<%[ There is required to provide both binary file and config file paths. ]%>" />
-                                                                       <com:TTextBox ID="BBconsJSONPath" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="BconsCfgPath" Text="<%[ Admin Bconsole config file path (usually bconsole.conf): ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCustomValidator ValidationGroup="ConfigGroup" ControlToValidate="BconsCfgPath" ClientValidationFunction="bjsontools_validator" Display="Dynamic" Text="<%[ There is required to provide both binary file and config file paths. ]%>" />
-                                                                       <com:TTextBox ID="BconsCfgPath" CssClass="textbox" />
-                                                               </div>
-                                                       </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TTextBox
+                                                               ID="BDirJSONPath"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       /> &nbsp;
+                                                       <span class="config_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <com:TActiveLabel
+                                                               ID="BDirJSONPathTestOk"
+                                                               Display="None"
+                                                               CssClass="w3-text-green"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </com:TActiveLabel>
+                                                       <com:TCustomValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="BDirJSONPath"
+                                                               ClientValidationFunction="bjsontools_validator"
+                                                               Display="Dynamic"
+                                                               Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                                       />
+                                                       <com:TActiveLabel
+                                                               ID="BDirJSONPathTestErr"
+                                                               Display="None"
+                                                               CssClass="w3-text-red"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Connection error ]%>
+                                                       </com:TActiveLabel>
                                                </div>
-                                               <span class="config_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                               <com:TActiveLabel ID="BBconsJSONPathTestOk" Display="None" CssClass="validate config_test_result" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></com:TActiveLabel>
-                                               <com:TActiveLabel ID="BBconsJSONPathTestErr" Display="None" CssClass="validator left" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Connection error ]%>" /> <%[ Connection error ]%></com:TActiveLabel>
-                                       </fieldset>
-                                       <div class="left button">
-                                               <com:BActiveButton ID="TestJSONToolsConfig" ValidationGroup="ConfigGroup" Text="<%[ Test configuration ]%>" OnClick="testJSONToolsCfg">
-                                                       <prop:ClientSide.OnLoading>
-                                                               bjsontools_hide_test_results();
-                                                               $('.config_test_loader').show();
-                                                       </prop:ClientSide.OnLoading>
-                                                       <prop:ClientSide.OnComplete>
-                                                               $('.config_test_loader').hide();
-                                                       </prop:ClientSide.OnComplete>
-                                               </com:BActiveButton>
                                        </div>
-                               </div>
-                       </div>
-               </com:TWizardStep>
-               <com:TWizardStep ID="Step5" Title="<%[ Step 5 - enable actions for components ]%>" StepType="Auto">
-                       <p><%[ Do you want to setup start, stop and restart actions for Bacula components? If you define them, there will be possible to call these actions via API interface and by Baculum Web as well. ]%></p>
-                       <div class="line left">
-                               <com:TRadioButton ID="ActionsNo" GroupName="SelectActions" Attributes.onclick="$('#configure_actions').hide();" /> <com:TLabel ForControl="ActionsNo" Text="<%[ No ]%>" />
-                       </div>
-                       <div class="line left">
-                               <com:TRadioButton ID="ActionsYes" GroupName="SelectActions" Attributes.onclick="$('#configure_actions').show();" /> <com:TLabel ForControl="ActionsYes" Text="<%[ Yes ]%>" />
-                       </div>
-                       <div id="configure_actions" style="display: <%=($this->ActionsYes->Checked === true) ? 'block' : 'none';%>">
-                               <div id="actions_fields">
-                                       <fieldset class="actions_field">
-                                               <legend><%[ General configuration ]%></legend>
-                                               <div class="actions_lines">
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="ActionsUseSudo" Text="<%[ Use sudo: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TCheckBox ID="ActionsUseSudo" /> <a href="javascript:void(0)" onclick="get_sudo_config('actions');"><%[ Get sudo configuration ]%></a>
-                                                               </div>
-                                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="DirCfgPath"
+                                                               Text="<%[ Main Director config file path (usually bacula-dir.conf): ]%>"
+                                                       />
                                                </div>
-                                       </fieldset>
-                                       <fieldset class="actions_field">
-                                               <legend>Director</legend>
-                                               <div class="actions_lines">
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="DirStartAction" Text="<%[ Director start command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="DirStartAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestDirStartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Start ]%>" OnCommand="testExecActionCommand" CommandParameter="dir_start">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestDirStartActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_dir_start .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_dir_start .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_dir_start">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="DirStopAction" Text="<%[ Director stop command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="DirStopAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestDirStopActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Stop ]%>" OnCommand="testExecActionCommand" CommandParameter="dir_stop">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestDirStopActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_dir_stop .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_dir_stop .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_dir_stop">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="DirRestartAction" Text="<%[ Director restart command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="DirRestartAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestDirRestartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Restart ]%>" OnCommand="testExecActionCommand" CommandParameter="dir_restart">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestDirRestartActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_dir_restart .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_dir_restart .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_dir_restart">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TTextBox
+                                                               ID="DirCfgPath"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       />
+                                                       <com:TCustomValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="DirCfgPath"
+                                                               ClientValidationFunction="bjsontools_validator"
+                                                               Display="Dynamic"
+                                                               Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                                       />
                                                </div>
-                                       </fieldset>
-                                       <fieldset class="actions_field">
-                                               <legend>Storage Daemon</legend>
-                                               <div class="actions_lines">
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="SdStartAction" Text="<%[ Storage Daemon start command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="SdStartAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestSdStartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Start ]%>" OnCommand="testExecActionCommand" CommandParameter="sd_start">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestSdStartActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_sd_start .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_sd_start .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_sd_start">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="SdStopAction" Text="<%[ Storage Daemon stop command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="SdStopAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestSdStopActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Stop ]%>" OnCommand="testExecActionCommand" CommandParameter="sd_stop">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestSdStopActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_sd_stop .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_sd_stop .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_sd_stop">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="SdRestartAction" Text="<%[ Storage Daemon restart command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="SdRestartAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestSdRestartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Restart ]%>" OnCommand="testExecActionCommand" CommandParameter="sd_restart">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestSdRestartActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_sd_restart .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_sd_restart .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_sd_restart">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
+                                       </div>
+                               </fieldset>
+                               <fieldset>
+                                       <legend>Storage Daemon</legend>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="BSdJSONPath"
+                                                               Text="<%[ bsdjson binary file path: ]%>"
+                                                       />
                                                </div>
-                                       </fieldset>
-                                       <fieldset class="actions_field">
-                                               <legend>File Daemon/Client</legend>
-                                               <div class="actions_lines">
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="FdStartAction" Text="<%[ File Daemon/Client start command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="FdStartAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestFdStartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Start ]%>" OnCommand="testExecActionCommand" CommandParameter="fd_start">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestFdStartActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_fd_start .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_fd_start .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_fd_start">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="FdStopAction" Text="<%[ File Daemon/Client stop command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="FdStopAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestFdStopActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Stop ]%>" OnCommand="testExecActionCommand" CommandParameter="fd_stop">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestFdStopActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_fd_stop .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_fd_stop .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_fd_stop">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
-                                                       <div class="line left">
-                                                               <div class="text"><com:TLabel ForControl="FdRestartAction" Text="<%[ File Daemon/Client restart command: ]%>" /></div>
-                                                               <div class="field">
-                                                                       <com:TTextBox ID="FdRestartAction" CssClass="textbox" />
-                                                               </div>
-                                                               <div class="button">
-                                                                       <com:TActiveButton ID="TestFdRestartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Restart ]%>" OnCommand="testExecActionCommand" CommandParameter="fd_restart">
-                                                                               <prop:ClientSide.OnLoading>
-                                                                                       actions_hide_test_results('<%=$this->TestFdRestartActionCommand->CommandParameter%>');
-                                                                                       $('#actions_test_result_fd_restart .action_test_loader').show();
-                                                                               </prop:ClientSide.OnLoading>
-                                                                               <prop:ClientSide.OnComplete>
-                                                                                       $('#actions_test_result_fd_restart .action_test_loader').hide();
-                                                                               </prop:ClientSide.OnComplete>
-                                                                       </com:TActiveButton>
-                                                               </div>
-                                                               <span id="actions_test_result_fd_restart">
-                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
-                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
-                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
-                                                                       <span class="action_result validator"></span>
-                                                               </span>
-                                                       </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TTextBox
+                                                               ID="BSdJSONPath"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       /> &nbsp;
+                                                       <span class="config_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <com:TCustomValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="BSdJSONPath"
+                                                               ClientValidationFunction="bjsontools_validator"
+                                                               Display="Dynamic"
+                                                               Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                                       />
+                                                       <com:TActiveLabel
+                                                               ID="BSdJSONPathTestOk"
+                                                               Display="None"
+                                                               CssClass="w3-text-green"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </com:TActiveLabel>
+                                                       <com:TActiveLabel
+                                                               ID="BSdJSONPathTestErr"
+                                                               Display="None"
+                                                               CssClass="w3-text-red"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Connection error ]%>
+                                                       </com:TActiveLabel>
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="SdCfgPath"
+                                                               Text="<%[ Main Storage Daemon config file path (usually bacula-sd.conf): ]%>"
+                                                       />
+                                               </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TTextBox
+                                                               ID="SdCfgPath"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       />
+                                                       <com:TCustomValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="SdCfgPath"
+                                                               ClientValidationFunction="bjsontools_validator"
+                                                               Display="Dynamic"
+                                                               Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                                       />
+                                               </div>
+                                       </div>
+                               </fieldset>
+                               <fieldset>
+                                       <legend>File Daemon/Client</legend>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="BFdJSONPath"
+                                                               Text="<%[ bfdjson binary file path: ]%>"
+                                                       />
                                                </div>
-                                       </fieldset>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TTextBox
+                                                               ID="BFdJSONPath"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       /> &nbsp;
+                                                       <com:TCustomValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="BFdJSONPath"
+                                                               ClientValidationFunction="bjsontools_validator"
+                                                               Display="Dynamic"
+                                                               Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                                       />
+                                                       <span class="config_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <com:TActiveLabel
+                                                               ID="BFdJSONPathTestOk"
+                                                               Display="None"
+                                                               CssClass="w3-text-green"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </com:TActiveLabel>
+                                                       <com:TActiveLabel
+                                                               ID="BFdJSONPathTestErr"
+                                                               Display="None"
+                                                               CssClass="w3-text-red"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Connection error ]%>
+                                                       </com:TActiveLabel>
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="FdCfgPath"
+                                                               Text="<%[ Main File Daemon config file path (usually bacula-fd.conf): ]%>"
+                                                       />
+                                               </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TTextBox
+                                                               ID="FdCfgPath"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       />
+                                                       <com:TCustomValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="FdCfgPath"
+                                                               ClientValidationFunction="bjsontools_validator"
+                                                               Display="Dynamic"
+                                                               Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                                       />
+                                               </div>
+                                       </div>
+                               </fieldset>
+                               <fieldset>
+                                       <legend>Bconsole</legend>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="BBconsJSONPath"
+                                                               Text="<%[ bbconsjson binary file path: ]%>"
+                                                       />
+                                               </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TCustomValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="BBconsJSONPath"
+                                                               ClientValidationFunction="bjsontools_validator"
+                                                               Display="Dynamic"
+                                                               Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                                       />
+                                                       <com:TTextBox
+                                                               ID="BBconsJSONPath"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       /> &nbsp;
+                                                       <span class="config_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <com:TActiveLabel
+                                                               ID="BBconsJSONPathTestOk"
+                                                               Display="None"
+                                                               CssClass="w3-text-green"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </com:TActiveLabel>
+                                                       <com:TActiveLabel
+                                                               ID="BBconsJSONPathTestErr"
+                                                               Display="None"
+                                                               CssClass="w3-text-red"
+                                                               EnableViewState="false"
+                                                       >
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Connection error ]%>
+                                                       </com:TActiveLabel>
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third">
+                                                       <com:TLabel
+                                                               ForControl="BconsCfgPath"
+                                                               Text="<%[ Admin Bconsole config file path (usually bconsole.conf): ]%>"
+                                                       />
+                                               </div>
+                                               <div class="w3-col w3-twothird">
+                                                       <com:TTextBox
+                                                               ID="BconsCfgPath"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="370px"
+                                                       />
+                                                       <com:TCustomValidator
+                                                               ValidationGroup="ConfigGroup"
+                                                               ControlToValidate="BconsCfgPath"
+                                                               ClientValidationFunction="bjsontools_validator"
+                                                               Display="Dynamic"
+                                                               Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                                       />
+                                               </div>
+                                       </div>
+                               </fieldset>
+                               <div class="w3-center w3-margin-top">
+                                       <com:TActiveLinkButton
+                                               ID="TestJSONToolsConfig"
+                                               ValidationGroup="ConfigGroup"
+                                               OnClick="testJSONToolsCfg"
+                                               CssClass="w3-button w3-green"
+                                       >
+                                               <prop:ClientSide.OnLoading>
+                                                       bjsontools_hide_test_results();
+                                                       $('.config_test_loader').show();
+                                               </prop:ClientSide.OnLoading>
+                                               <prop:ClientSide.OnComplete>
+                                                       $('.config_test_loader').hide();
+                                               </prop:ClientSide.OnComplete>
+                                               <i class="fas fa-play"></i> &nbsp;<%[ Test configuration ]%>
+                                       </com:TActiveLinkButton>
                                </div>
                        </div>
                </com:TWizardStep>
-               <com:TWizardStep ID="Step6" Title="<%[ Step 6 - authentication to API ]%>" StepType="Auto">
-                       <div class="line left" style="width:500px; margin: 0 auto">
+               <com:TWizardStep ID="Step5" Title="<%[ Step 5 - authentication to API ]%>" StepType="Auto">
+                       <div class="w3-container w3-section">
                                <com:TActiveRadioButton
                                        ID="AuthOAuth2"
                                        GroupName="SelectAuth"
+                                       CssClass="w3-radio"
                                        Attributes.onclick="$('#configure_basic_auth').hide();$('#configure_oauth2_auth').show();"
                                />
                                <com:TLabel
                                        Text="<%[ Use OAuth2 for authorization and authentication ]%>"
                                />
                        </div>
-                       <div class="line left" style="width:500px; margin: 0 auto">
+                       <div class="w3-container w3-section">
                                <com:TActiveRadioButton
                                        ID="AuthBasic"
                                        GroupName="SelectAuth"
+                                       CssClass="w3-radio"
                                        Checked="true"
                                        Attributes.onclick="$('#configure_oauth2_auth').hide();$('#configure_basic_auth').show();"
                                />
                        </div>
                        <div id="configure_basic_auth" style="display: <%=($this->AuthBasic->Checked === true) ? '' : 'none';%>">
                                <com:TPanel Visible="<%=$this->first_run%>">
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="APILogin" Text="<%[ Administration login: ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APILogin" Text="<%[ Administration login: ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="APILogin"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                CausesValidation="false"
+                                                               Width="370px"
                                                        />
                                                        <com:TRequiredFieldValidator
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="APILogin"
                                                                ValidationGroup="Basic"
                                                                Text="<%[ Please enter login. ]%>"
                                                        />
                                                        <com:TRegularExpressionValidator
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="APILogin"
                                                                ValidationGroup="Basic"
                                                                RegularExpression="<%=BasicAPIUserConfig::USER_PATTERN%>"
                                                        />
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="APIPassword" Text="<%[ Administration password: ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIPassword" Text="<%[ Administration password: ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="APIPassword"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                TextMode="Password"
                                                                MaxLength="60"
                                                                PersistPassword="true"
+                                                               Width="370px"
                                                        />
                                                        <com:TRequiredFieldValidator
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="APIPassword"
                                                                ValidationGroup="Basic"
                                                                Text="<%[ Please enter password. ]%>"
                                                        />
                                                        <com:TRegularExpressionValidator
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="APIPassword"
                                                                RegularExpression="[\S\s]{5,60}"
                                                                ValidationGroup="Basic"
                                                        />
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="RetypeAPIPassword" Text="<%[ Retype administration password: ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="RetypeAPIPassword" Text="<%[ Retype administration password: ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="RetypeAPIPassword"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                TextMode="Password"
                                                                MaxLength="60"
                                                                PersistPassword="true"
+                                                               Width="370px"
                                                        />
                                                        <com:TRequiredFieldValidator
                                                                ID="RetypePasswordRequireValidator"
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="RetypeAPIPassword"
                                                                ValidationGroup="Basic"
                                                                Text="<%[ Please enter retype password. ]%>"
                                                        />
                                                        <com:TRegularExpressionValidator
                                                                ID="RetypePasswordRegexpValidator"
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="RetypeAPIPassword"
                                                                RegularExpression="[\S\s]{5,60}"
                                                                ValidationGroup="Basic"
                                                        />
                                                        <com:TCompareValidator
                                                                ID="RetypePasswordCustomValidator"
-                                                               CssClass="validator-block"
                                                                Display="Dynamic"
-                                                               ControlCssClass="invalidate"
                                                                ControlToValidate="RetypeAPIPassword"
                                                                ControlToCompare="APIPassword"
                                                                ValidationGroup="Basic"
                                <com:TPanel Visible="<%=($this->first_run || $this->add_auth_params)%>">
                                        <h3><%[ First client registration ]%></h3>
                                        <h4><%[ Please do not forget to disable HTTP Basic auth in the API web server config. Othwerise OAuth2 will not work. ]%></h4>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="APIOAuth2ClientId" Text="<%[ OAuth2 Client ID: ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2ClientId" Text="<%[ OAuth2 Client ID: ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="APIOAuth2ClientId"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                CausesValidation="false"
                                                                MaxLength="32"
+                                                               Width="370px"
                                                        />
                                                        <com:TRequiredFieldValidator
                                                                CssClass="validator-block"
                                                        <a href="javascript:void(0)" onclick="document.getElementById('<%=$this->APIOAuth2ClientId->ClientID%>').value = get_random_string('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 32); return false;"><%[ generate ]%></a>
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="APIOAuth2ClientSecret" Text="<%[ OAuth2 Client Secret: ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2ClientSecret" Text="<%[ OAuth2 Client Secret: ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="APIOAuth2ClientSecret"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                CausesValidation="false"
                                                                MaxLength="50"
+                                                               Width="370px"
                                                        />
                                                        <com:TRequiredFieldValidator
                                                                CssClass="validator-block"
                                                        <a href="javascript:void(0)" onclick="document.getElementById('<%=$this->APIOAuth2ClientSecret->ClientID%>').value = get_random_string('ABCDEFabcdef0123456789', 40); return false;"><%[ generate ]%></a>
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="APIOAuth2RedirectURI" Text="<%[ OAuth2 Redirect URI (example: https://baculumgui:9095/web/redirect): ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2RedirectURI" Text="<%[ OAuth2 Redirect URI (example: https://baculumgui:9095/web/redirect): ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="APIOAuth2RedirectURI"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                CausesValidation="false"
+                                                               Width="370px"
                                                        />
                                                        <com:TRequiredFieldValidator
                                                                CssClass="validator-block"
                                                        />
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="APIOAuth2Scope" Text="<%[ OAuth2 scopes (space separated): ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2Scope" Text="<%[ OAuth2 scopes (space separated): ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="APIOAuth2Scope"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                CausesValidation="false"
                                                                TextMode="MultiLine"
+                                                               Width="370px"
                                                        />
                                                        <a href="javascript:void(0)" onclick="set_scopes('<%=$this->APIOAuth2Scope->ClientID%>'); return false;" style="vertical-align: top"><%[ set all scopes ]%></a>
                                                        <com:TRequiredFieldValidator
                                                        />
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="APIOAuth2BconsoleCfgPath" Text="<%[ Dedicated Bconsole config file path: ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2BconsoleCfgPath" Text="<%[ Dedicated Bconsole config file path: ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="APIOAuth2BconsoleCfgPath"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                CausesValidation="false"
                                                                ValidationGroup="OAuth2"
+                                                               Width="370px"
                                                        /> <%[ (optional) ]%>
                                                </div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><com:TLabel ForControl="APIOAuth2Name" Text="<%[ Short name: ]%>" /></div>
-                                               <div class="field">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2Name" Text="<%[ Short name: ]%>" /></div>
+                                               <div class="w3-col w3-twothird">
                                                        <com:TTextBox
                                                                ID="APIOAuth2Name"
-                                                               CssClass="textbox"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
                                                                CausesValidation="false"
+                                                               Width="370px"
                                                        /> <%[ (optional) ]%>
                                                </div>
                                        </div>
                                        } else if (oauth2.checked === true) {
                                                validation_group = 'OAuth2';
                                        }
-                                       return Prado.Validation.validate(Prado.Validation.getForm(), validation_group);
+                                       var ret = true;
+                                       if (Prado.Validation) {
+                                               ret = Prado.Validation.validate(Prado.Validation.getForm(), validation_group);
+                                       }
+                                       return ret;
                                }
                        </script>
                </com:TWizardStep>
                        <fieldset>
                                <legend><%[ Catalog API ]%></legend>
                                <div style="display: <%=($this->DatabaseYes->Checked ? 'block' : 'none')%>">
-                                       <div class="line">
-                                               <div class="text"><%[ Database type: ]%></div>
-                                               <div class="field bold"><%=$this->getDbNameByType($this->DBType->SelectedValue)%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Database type: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->getDbNameByType($this->DBType->SelectedValue)%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Database name: ]%></div>
-                                               <div class="field bold"><%=$this->DBName->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Database name: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->DBName->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Login: ]%></div>
-                                               <div class="field bold"><%=$this->isSQLiteType($this->DBType->SelectedValue) === false ? $this->Login->Text : '-'%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Login: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->isSQLiteType($this->DBType->SelectedValue) === false ? $this->Login->Text : '-'%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Password: ]%></div>
-                                               <div class="field bold"><%=$this->isSQLiteType($this->DBType->SelectedValue) === false ? preg_replace('/.{1}/', '*', $this->Password->Text) : '-'%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Password: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->isSQLiteType($this->DBType->SelectedValue) === false ? preg_replace('/.{1}/', '*', $this->Password->Text) : '-'%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ IP address (or hostname): ]%></div>
-                                               <div class="field bold"><%=$this->isSQLiteType($this->DBType->SelectedValue) === false ? $this->IP->Text : '-'%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ IP address (or hostname): ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->isSQLiteType($this->DBType->SelectedValue) === false ? $this->IP->Text : '-'%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Database port: ]%></div>
-                                               <div class="field bold"><%=$this->isSQLiteType($this->DBType->SelectedValue) === false ? $this->Port->Text : '-'%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Database port: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->isSQLiteType($this->DBType->SelectedValue) === false ? $this->Port->Text : '-'%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Database file path (SQLite only): ]%></div>
-                                               <div class="field bold"><%=$this->isSQLiteType($this->DBType->SelectedValue) === true ? $this->DBPath->Text : '-'%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Database file path (SQLite only): ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->isSQLiteType($this->DBType->SelectedValue) === true ? $this->DBPath->Text : '-'%></strong></div>
                                        </div>
                                </div>
                                <div style="display: <%=($this->DatabaseNo->Checked ? 'block' : 'none')%>">
-                                       <div class="line">
-                                               <div class="text"><%[ Catalog access: ]%></div>
-                                               <div class="field bold"><%[ Disabled ]%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Catalog access: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%[ Disabled ]%></strong></div>
                                        </div>
                                </div>
                        </fieldset>
                        <fieldset>
                                <legend><%[ Console API ]%></legend>
                                <div style="display: <%=($this->ConsoleYes->Checked ? 'block' : 'none')%>">
-                                       <div class="line">
-                                               <div class="text"><%[ Bconsole binary file path: ]%></div>
-                                               <div class="field bold"><%=$this->BconsolePath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Bconsole binary file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->BconsolePath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Bconsole admin config file path: ]%></div>
-                                               <div class="field bold"><%=$this->BconsoleConfigPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Bconsole admin config file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->BconsoleConfigPath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Use sudo for bconsole requests: ]%></div>
-                                               <div class="field bold"><%=($this->UseSudo->Checked === true) ? 'yes' : 'no'%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Use sudo for bconsole requests: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=($this->UseSudo->Checked === true) ? 'yes' : 'no'%></strong></div>
                                        </div>
                                </div>
                                <div style="display: <%=($this->ConsoleNo->Checked ? 'block' : 'none')%>">
-                                       <div class="line">
-                                               <div class="text"><%[ Console access: ]%></div>
-                                               <div class="field bold"><%[ Disabled ]%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Console access: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%[ Disabled ]%></strong></div>
                                        </div>
                                </div>
                        </fieldset>
                        <fieldset>
                                <legend><%[ Config API ]%></legend>
                                <div style="display: <%=($this->ConfigYes->Checked ? 'block' : 'none')%>">
-                                       <div class="line">
-                                               <div class="text"><%[ Directory path for new config files: ]%></div>
-                                               <div class="field bold"><%=$this->BConfigDir->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Directory path for new config files: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->BConfigDir->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Use sudo for Bacula JSON tools: ]%></div>
-                                               <div class="field bold"><%=($this->BJSONUseSudo->Checked === true) ? 'yes' : 'no'%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Use sudo for Bacula JSON tools: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=($this->BJSONUseSudo->Checked === true) ? 'yes' : 'no'%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ bdirjson binary file path: ]%></div>
-                                               <div class="field bold"><%=$this->BDirJSONPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ bdirjson binary file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->BDirJSONPath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Main Director config file path: ]%></div>
-                                               <div class="field bold"><%=$this->DirCfgPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Main Director config file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->DirCfgPath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ bsdjson binary file path: ]%></div>
-                                               <div class="field bold"><%=$this->BSdJSONPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ bsdjson binary file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->BSdJSONPath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Main Storage Daemon config file path: ]%></div>
-                                               <div class="field bold"><%=$this->SdCfgPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Main Storage Daemon config file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->SdCfgPath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ bfdjson binary file path: ]%></div>
-                                               <div class="field bold"><%=$this->BFdJSONPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ bfdjson binary file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->BFdJSONPath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Main Client config file path: ]%></div>
-                                               <div class="field bold"><%=$this->FdCfgPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Main Client config file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->FdCfgPath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ bbconsjson binary file path: ]%></div>
-                                               <div class="field bold"><%=$this->BBconsJSONPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ bbconsjson binary file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->BBconsJSONPath->Text%></strong></div>
                                        </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Main Bconsole config file path: ]%></div>
-                                               <div class="field bold"><%=$this->BconsCfgPath->Text%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Main Bconsole config file path: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%=$this->BconsCfgPath->Text%></strong></div>
                                        </div>
                                </div>
                                <div style="display: <%=($this->ConfigNo->Checked ? 'block' : 'none')%>">
-                                       <div class="line">
-                                               <div class="text"><%[ Config access: ]%></div>
-                                               <div class="field bold"><%[ Disabled ]%></div>
-                                       </div>
-                               </div>
-                       </fieldset>
-                       <fieldset>
-                               <legend><%[ Actions ]%></legend>
-                               <div style="display: <%=($this->ActionsYes->Checked ? 'block' : 'none')%>">
-                                       <div class="line">
-                                               <div class="text"><%[ Use sudo for actions: ]%></div>
-                                               <div class="field bold"><%=($this->ActionsUseSudo->Checked === true) ? 'yes' : 'no'%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Director start command: ]%></div>
-                                               <div class="field bold"><%=$this->DirStartAction->Text%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Director stop command: ]%></div>
-                                               <div class="field bold"><%=$this->DirStopAction->Text%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Director restart command: ]%></div>
-                                               <div class="field bold"><%=$this->DirRestartAction->Text%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Storage Daemon start command: ]%></div>
-                                               <div class="field bold"><%=$this->SdStartAction->Text%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Storage Daemon stop command: ]%></div>
-                                               <div class="field bold"><%=$this->SdStopAction->Text%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ Storage Daemon restart command: ]%></div>
-                                               <div class="field bold"><%=$this->SdRestartAction->Text%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ File Daemon/Client start command: ]%></div>
-                                               <div class="field bold"><%=$this->FdStartAction->Text%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ File Daemon/Client stop command: ]%></div>
-                                               <div class="field bold"><%=$this->FdStopAction->Text%></div>
-                                       </div>
-                                       <div class="line">
-                                               <div class="text"><%[ File Daemon/Client restart command: ]%></div>
-                                               <div class="field bold"><%=$this->FdRestartAction->Text%></div>
-                                       </div>
-                               </div>
-                               <div style="display: <%=($this->ActionsNo->Checked ? 'block' : 'none')%>">
-                                       <div class="line">
-                                               <div class="text"><%[ Actions setting: ]%></div>
-                                               <div class="field bold"><%[ Disabled ]%></div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><%[ Config access: ]%></div>
+                                               <div class="w3-col w3-third"><strong><%[ Disabled ]%></strong></div>
                                        </div>
                                </div>
                        </fieldset>
                        <fieldset>
                                <legend><%[ Authentication to Baculum REST API ]%></legend>
-                               <div class="line">
-                                       <div class="text"><%[ Authentication type: ]%></div>
-                                       <div class="field bold">
-                                               <%=$this->AuthBasic->Checked ? 'HTTP Basic' : ''%>
-                                               <%=$this->AuthOAuth2->Checked ? 'OAuth2' : ''%>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><%[ Authentication type: ]%></div>
+                                       <div class="w3-col w3-third">
+                                               <strong>
+                                                       <%=$this->AuthBasic->Checked ? 'HTTP Basic' : ''%>
+                                                       <%=$this->AuthOAuth2->Checked ? 'OAuth2' : ''%>
+                                               </strong>
                                        </div>
                                </div>
                                <com:TPanel Visible="<%=$this->first_run%>">
                                        <div style="display: <%=$this->AuthBasic->Checked ? 'block' : 'none'%>">
-                                               <div class="line">
-                                                       <div class="text"><%[ Administration login: ]%></div>
-                                                       <div class="field bold"><%=$this->APILogin->Text%></div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third"><%[ Administration login: ]%></div>
+                                                       <div class="w3-col w3-third"><strong><%=$this->APILogin->Text%></strong></div>
                                                </div>
-                                               <div class="line">
-                                                       <div class="text"><%[ Administration password: ]%></div>
-                                                       <div class="field bold"><%=preg_replace('/.{1}/', '*', $this->APIPassword->Text)%></div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third"><%[ Administration password: ]%></div>
+                                                       <div class="w3-col w3-third"><strong><%=preg_replace('/.{1}/', '*', $this->APIPassword->Text)%></strong></div>
                                                </div>
                                        </div>
                                </com:TPanel>
                                <com:TPanel Visible="<%=($this->first_run || $this->add_auth_params)%>">
                                        <div style="display: <%=$this->AuthOAuth2->Checked ? 'block' : 'none'%>">
-                                               <div class="line">
-                                                       <div class="text">Client ID:</div>
-                                                       <div class="field bold"><%=$this->APIOAuth2ClientId->Text%></div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third">Client ID:</div>
+                                                       <div class="w3-col w3-third"><strong><%=$this->APIOAuth2ClientId->Text%></strong></div>
                                                </div>
-                                               <div class="line">
-                                                       <div class="text">Client Secret:</div>
-                                                       <div class="field bold"><%=preg_replace('/.{1}/', '*', $this->APIOAuth2ClientSecret->Text)%></div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third">Client Secret:</div>
+                                                       <div class="w3-col w3-third"><strong><%=preg_replace('/.{1}/', '*', $this->APIOAuth2ClientSecret->Text)%></strong></div>
                                                </div>
-                                               <div class="line">
-                                                       <div class="text">Redirect URI:</div>
-                                                       <div class="field bold"><%=$this->APIOAuth2RedirectURI->Text%></div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third">Redirect URI:</div>
+                                                       <div class="w3-col w3-third"><strong><%=$this->APIOAuth2RedirectURI->Text%></strong></div>
                                                </div>
-                                               <div class="line">
-                                                       <div class="text">Scope:</div>
-                                                       <div class="field bold"><%=$this->APIOAuth2Scope->Text%></div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third">Scope:</div>
+                                                       <div class="w3-col w3-third"><strong><%=$this->APIOAuth2Scope->Text%></strong></div>
                                                </div>
-                                               <div class="line">
-                                                       <div class="text"><%[ Dedicated Bconsole config file path: ]%></div>
-                                                       <div class="field bold"><%=!empty($this->APIOAuth2BconsoleCfgPath->Text) ? $this->APIOAuth2BconsoleCfgPath->Text : '-'%></div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third"><%[ Dedicated Bconsole config file path: ]%></div>
+                                                       <div class="w3-col w3-third"><strong><%=!empty($this->APIOAuth2BconsoleCfgPath->Text) ? $this->APIOAuth2BconsoleCfgPath->Text : '-'%></strong></div>
                                                </div>
                                        </div>
                                </com:TPanel>
        ID="SudoConfigPopup"
        Options.title="<%[ Sudo configuration ]%>"
        Options.autoOpen="false",
-       Options.minWidth="600"
+       Options.minWidth="820"
        Options.minHeight="200"
 >
        <p><%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%></p>
@@ -1285,17 +1381,6 @@ function get_sudo_config(type) {
                        '<%=$this->BSdJSONPath->ClientID%>',
                        '<%=$this->BFdJSONPath->ClientID%>',
                        '<%=$this->BBconsJSONPath->ClientID%>',
-               ],
-               actions: [
-                       '<%=$this->DirStartAction->ClientID%>',
-                       '<%=$this->DirStopAction->ClientID%>',
-                       '<%=$this->DirRestartAction->ClientID%>',
-                       '<%=$this->SdStartAction->ClientID%>',
-                       '<%=$this->SdStopAction->ClientID%>',
-                       '<%=$this->SdRestartAction->ClientID%>',
-                       '<%=$this->FdStartAction->ClientID%>',
-                       '<%=$this->FdStopAction->ClientID%>',
-                       '<%=$this->FdRestartAction->ClientID%>'
                ]
        }
        var val, pre;
index a615747e708e05538f78b91a5aea3f30e61cbd98..dfe8720f032c86bdbdc325a451b782916587214f 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
@@ -94,130 +94,86 @@ class APIInstallWizard extends BaculumAPIPage {
        public function onLoad($param) {
                parent::onLoad($param);
                $this->Port->setViewState('port', $this->Port->Text);
-               if(!$this->IsPostBack && !$this->IsCallBack) {
-                       if($this->first_run === true) {
-                               $this->DBName->Text = self::DEFAULT_DB_NAME;
-                               $this->Login->Text = self::DEFAULT_DB_LOGIN;
-                               $this->BconsolePath->Text = self::DEFAULT_BCONSOLE_BIN;
-                               $this->BconsoleConfigPath->Text = self::DEFAULT_BCONSOLE_CONF;
-                               $this->BDirJSONPath->Text = self::DEFAULT_BDIRJSON_BIN;
-                               $this->DirCfgPath->Text = self::DEFAULT_DIR_CONF;
-                               $this->BSdJSONPath->Text = self::DEFAULT_BSDJSON_BIN;
-                               $this->SdCfgPath->Text = self::DEFAULT_SD_CONF;
-                               $this->BFdJSONPath->Text = self::DEFAULT_BFDJSON_BIN;
-                               $this->FdCfgPath->Text = self::DEFAULT_FD_CONF;
-                               $this->BBconsJSONPath->Text = self::DEFAULT_BBCONJSON_BIN;
-                               $this->BconsCfgPath->Text = self::DEFAULT_BCONSOLE_CONF;
-
-                               $this->DirStartAction->Text = self::DEFAULT_ACTION_DIR_START;
-                               $this->DirStopAction->Text = self::DEFAULT_ACTION_DIR_STOP;
-                               $this->DirRestartAction->Text = self::DEFAULT_ACTION_DIR_RESTART;
-                               $this->SdStartAction->Text = self::DEFAULT_ACTION_SD_START;
-                               $this->SdStopAction->Text = self::DEFAULT_ACTION_SD_STOP;
-                               $this->SdRestartAction->Text = self::DEFAULT_ACTION_SD_RESTART;
-                               $this->FdStartAction->Text = self::DEFAULT_ACTION_FD_START;
-                               $this->FdStopAction->Text = self::DEFAULT_ACTION_FD_STOP;
-                               $this->FdRestartAction->Text = self::DEFAULT_ACTION_FD_RESTART;
+               if ($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
 
+               if ($this->first_run === true) {
+                       $this->DBName->Text = self::DEFAULT_DB_NAME;
+                       $this->Login->Text = self::DEFAULT_DB_LOGIN;
+                       $this->BconsolePath->Text = self::DEFAULT_BCONSOLE_BIN;
+                       $this->BconsoleConfigPath->Text = self::DEFAULT_BCONSOLE_CONF;
+                       $this->BDirJSONPath->Text = self::DEFAULT_BDIRJSON_BIN;
+                       $this->DirCfgPath->Text = self::DEFAULT_DIR_CONF;
+                       $this->BSdJSONPath->Text = self::DEFAULT_BSDJSON_BIN;
+                       $this->SdCfgPath->Text = self::DEFAULT_SD_CONF;
+                       $this->BFdJSONPath->Text = self::DEFAULT_BFDJSON_BIN;
+                       $this->FdCfgPath->Text = self::DEFAULT_FD_CONF;
+                       $this->BBconsJSONPath->Text = self::DEFAULT_BBCONJSON_BIN;
+                       $this->BconsCfgPath->Text = self::DEFAULT_BCONSOLE_CONF;
+
+                       $this->DatabaseNo->Checked = true;
+                       $this->ConsoleNo->Checked = true;
+                       $this->ConfigNo->Checked = true;
+               } else {
+                       // Database param settings
+                       if ($this->config['db']['enabled'] == 1) {
+                               $this->DatabaseYes->Checked = true;
+                               $this->DatabaseNo->Checked = false;
+                       } else {
+                               $this->DatabaseYes->Checked = false;
                                $this->DatabaseNo->Checked = true;
-                               $this->ConsoleNo->Checked = true;
-                               $this->ConfigNo->Checked = true;
-                               $this->ActionsNo->Checked = true;
+                       }
+                       $this->DBType->SelectedValue = $this->config['db']['type'];
+                       $this->DBName->Text = $this->config['db']['name'];
+                       $this->Login->Text = $this->config['db']['login'];
+                       $this->Password->Text = $this->config['db']['password'];
+                       $this->IP->Text = $this->config['db']['ip_addr'];
+                       $this->Port->Text = $this->config['db']['port'];
+                       $this->Port->setViewState('port', $this->config['db']['port']);
+                       $this->DBPath->Text = $this->config['db']['path'];
+
+                       // Bconsole param settings
+                       if ($this->config['bconsole']['enabled'] == 1) {
+                               $this->ConsoleYes->Checked = true;
+                               $this->ConsoleNo->Checked = false;
                        } else {
-                               // Database param settings
-                               if ($this->config['db']['enabled'] == 1) {
-                                       $this->DatabaseYes->Checked = true;
-                                       $this->DatabaseNo->Checked = false;
-                               } else {
-                                       $this->DatabaseYes->Checked = false;
-                                       $this->DatabaseNo->Checked = true;
-                               }
-                               $this->DBType->SelectedValue = $this->config['db']['type'];
-                               $this->DBName->Text = $this->config['db']['name'];
-                               $this->Login->Text = $this->config['db']['login'];
-                               $this->Password->Text = $this->config['db']['password'];
-                               $this->IP->Text = $this->config['db']['ip_addr'];
-                               $this->Port->Text = $this->config['db']['port'];
-                               $this->Port->setViewState('port', $this->config['db']['port']);
-                               $this->DBPath->Text = $this->config['db']['path'];
-
-                               // Bconsole param settings
-                               if ($this->config['bconsole']['enabled'] == 1) {
-                                       $this->ConsoleYes->Checked = true;
-                                       $this->ConsoleNo->Checked = false;
-                               } else {
-                                       $this->ConsoleYes->Checked = false;
-                                       $this->ConsoleNo->Checked = true;
-                               }
-                               $this->BconsolePath->Text = $this->config['bconsole']['bin_path'];
-                               $this->BconsoleConfigPath->Text = $this->config['bconsole']['cfg_path'];
-                               $this->UseSudo->Checked = $this->getPage()->config['bconsole']['use_sudo'] == 1;
-
-                               $api_config = $this->getModule('api_config');
+                               $this->ConsoleYes->Checked = false;
+                               $this->ConsoleNo->Checked = true;
+                       }
+                       $this->BconsolePath->Text = $this->config['bconsole']['bin_path'];
+                       $this->BconsoleConfigPath->Text = $this->config['bconsole']['cfg_path'];
+                       $this->UseSudo->Checked = $this->getPage()->config['bconsole']['use_sudo'] == 1;
 
-                               // JSONTools param settings
-                               if ($api_config->isJSONToolsEnabled() === true) {
-                                       $this->ConfigYes->Checked = true;
-                                       $this->ConfigNo->Checked = false;
-                               } else {
-                                       $this->ConfigYes->Checked = false;
-                                       $this->ConfigNo->Checked = true;
-                               }
-                               $this->BConfigDir->Text = $this->config['jsontools']['bconfig_dir'];
-                               $this->BJSONUseSudo->Checked = ($this->config['jsontools']['use_sudo'] == 1);
-                               $this->BDirJSONPath->Text = $this->config['jsontools']['bdirjson_path'];
-                               $this->DirCfgPath->Text = $this->config['jsontools']['dir_cfg_path'];
-                               $this->BSdJSONPath->Text = $this->config['jsontools']['bsdjson_path'];
-                               $this->SdCfgPath->Text = $this->config['jsontools']['sd_cfg_path'];
-                               $this->BFdJSONPath->Text = $this->config['jsontools']['bfdjson_path'];
-                               $this->FdCfgPath->Text = $this->config['jsontools']['fd_cfg_path'];
-                               $this->BBconsJSONPath->Text = $this->config['jsontools']['bbconsjson_path'];
-                               $this->BconsCfgPath->Text = $this->config['jsontools']['bcons_cfg_path'];
-
-                               if ($api_config->isActionsConfigured()) {
-                                       // Action params
-                                       if ($api_config->isActionsEnabled() === true) {
-                                               $this->ActionsYes->Checked = true;
-                                               $this->ActionsNo->Checked = false;
-                                       } else {
-                                               $this->ActionsYes->Checked = false;
-                                               $this->ActionsNo->Checked = true;
-                                       }
-
-                                       $this->ActionsUseSudo->Checked = ($this->config['actions']['use_sudo'] == 1);
-                                       $this->DirStartAction->Text = $this->config['actions']['dir_start'];
-                                       $this->DirStopAction->Text = $this->config['actions']['dir_stop'];
-                                       $this->DirRestartAction->Text = $this->config['actions']['dir_restart'];
-                                       $this->SdStartAction->Text = $this->config['actions']['sd_start'];
-                                       $this->SdStopAction->Text = $this->config['actions']['sd_stop'];
-                                       $this->SdRestartAction->Text = $this->config['actions']['sd_restart'];
-                                       $this->FdStartAction->Text = $this->config['actions']['fd_start'];
-                                       $this->FdStopAction->Text = $this->config['actions']['fd_stop'];
-                                       $this->FdRestartAction->Text = $this->config['actions']['fd_restart'];
-                               } else {
-                                       $this->ActionsYes->Checked = false;
-                                       $this->ActionsNo->Checked = true;
-                                       $this->ActionsUseSudo->Checked = false;
-                                       $this->DirStartAction->Text = self::DEFAULT_ACTION_DIR_START;
-                                       $this->DirStopAction->Text = self::DEFAULT_ACTION_DIR_STOP;
-                                       $this->DirRestartAction->Text = self::DEFAULT_ACTION_DIR_RESTART;
-                                       $this->SdStartAction->Text = self::DEFAULT_ACTION_SD_START;
-                                       $this->SdStopAction->Text = self::DEFAULT_ACTION_SD_STOP;
-                                       $this->SdRestartAction->Text = self::DEFAULT_ACTION_SD_RESTART;
-                                       $this->FdStartAction->Text = self::DEFAULT_ACTION_FD_START;
-                                       $this->FdStopAction->Text = self::DEFAULT_ACTION_FD_STOP;
-                                       $this->FdRestartAction->Text = self::DEFAULT_ACTION_FD_RESTART;
-                               }
+                       $api_config = $this->getModule('api_config');
 
-                               if ($this->config['api']['auth_type'] === 'basic') {
-                                       // API basic auth data
-                                       $this->AuthBasic->Checked = true;
-                                       $this->AuthOAuth2->Checked = false;
-                               } elseif ($this->config['api']['auth_type'] === 'oauth2') {
-                                       // API oauth2 auth data
-                                       $this->AuthBasic->Checked = false;
-                                       $this->AuthOAuth2->Checked = true;
-                               }
+                       // JSONTools param settings
+                       if ($api_config->isJSONToolsEnabled() === true) {
+                               $this->ConfigYes->Checked = true;
+                               $this->ConfigNo->Checked = false;
+                       } else {
+                               $this->ConfigYes->Checked = false;
+                               $this->ConfigNo->Checked = true;
+                       }
+                       $this->BConfigDir->Text = $this->config['jsontools']['bconfig_dir'];
+                       $this->BJSONUseSudo->Checked = ($this->config['jsontools']['use_sudo'] == 1);
+                       $this->BDirJSONPath->Text = $this->config['jsontools']['bdirjson_path'];
+                       $this->DirCfgPath->Text = $this->config['jsontools']['dir_cfg_path'];
+                       $this->BSdJSONPath->Text = $this->config['jsontools']['bsdjson_path'];
+                       $this->SdCfgPath->Text = $this->config['jsontools']['sd_cfg_path'];
+                       $this->BFdJSONPath->Text = $this->config['jsontools']['bfdjson_path'];
+                       $this->FdCfgPath->Text = $this->config['jsontools']['fd_cfg_path'];
+                       $this->BBconsJSONPath->Text = $this->config['jsontools']['bbconsjson_path'];
+                       $this->BconsCfgPath->Text = $this->config['jsontools']['bcons_cfg_path'];
+
+                       if ($this->config['api']['auth_type'] === 'basic') {
+                               // API basic auth data
+                               $this->AuthBasic->Checked = true;
+                               $this->AuthOAuth2->Checked = false;
+                       } elseif ($this->config['api']['auth_type'] === 'oauth2') {
+                               // API oauth2 auth data
+                               $this->AuthBasic->Checked = false;
+                               $this->AuthOAuth2->Checked = true;
                        }
                }
        }
@@ -237,8 +193,7 @@ class APIInstallWizard extends BaculumAPIPage {
                        'api' => array(),
                        'db' => array(),
                        'bconsole' => array(),
-                       'jsontools' => array(),
-                       'actions' => array()
+                       'jsontools' => array()
                );
                if ($this->AuthBasic->Checked) {
                        $cfg_data['api']['auth_type'] =  'basic';
@@ -270,17 +225,6 @@ class APIInstallWizard extends BaculumAPIPage {
                $cfg_data['jsontools']['fd_cfg_path'] = $this->FdCfgPath->Text;
                $cfg_data['jsontools']['bbconsjson_path'] = $this->BBconsJSONPath->Text;
                $cfg_data['jsontools']['bcons_cfg_path'] = $this->BconsCfgPath->Text;
-               $cfg_data['actions']['enabled'] = (integer)($this->ActionsYes->Checked === true);
-               $cfg_data['actions']['use_sudo'] = (integer)($this->ActionsUseSudo->Checked === true);
-               $cfg_data['actions']['dir_start'] = $this->DirStartAction->Text;
-               $cfg_data['actions']['dir_stop'] = $this->DirStopAction->Text;
-               $cfg_data['actions']['dir_restart'] = $this->DirRestartAction->Text;
-               $cfg_data['actions']['sd_start'] = $this->SdStartAction->Text;
-               $cfg_data['actions']['sd_stop'] = $this->SdStopAction->Text;
-               $cfg_data['actions']['sd_restart'] = $this->SdRestartAction->Text;
-               $cfg_data['actions']['fd_start'] = $this->FdStartAction->Text;
-               $cfg_data['actions']['fd_stop'] = $this->FdStopAction->Text;
-               $cfg_data['actions']['fd_restart'] = $this->FdRestartAction->Text;
 
                $ret = $this->getModule('api_config')->setConfig($cfg_data);
                if ($ret) {
@@ -388,7 +332,6 @@ class APIInstallWizard extends BaculumAPIPage {
                $this->PortValidator->Display = ($this->Port->Enabled === true) ? 'Dynamic' : 'None';
                $this->IPValidator->Display = ($this->IP->Enabled === true) ? 'Dynamic' : 'None';
                $this->DBPathValidator->Display = ($this->DBPath->Enabled === true) ? 'Dynamic' : 'None';
-               $this->DbTestResultOk->Display = 'None';
                $this->DbTestResultErr->Display = 'None';
                $this->Step2Content->render($param->NewWriter);
        }
@@ -415,15 +358,21 @@ class APIInstallWizard extends BaculumAPIPage {
                        try {
                                $is_validate = $this->getModule('db')->testDbConnection($db_params);
                        } catch (BAPIException $e) {
-                               $emsg = $e;
+                               $emsg = $e->getErrorMessage();
                        }
                }
-               $this->DbTestResultOk->Display = ($is_validate === true) ? 'Dynamic' : 'None';
-               if ($emsg instanceof BAPIException) {
+               if (!empty($emsg)) {
                        $this->DbTestResultErr->Text = $emsg;
                }
-               $this->DbTestResultErr->Display = ($is_validate === false) ? 'Dynamic' : 'None';
-               $this->Step2Content->render($param->NewWriter);
+               if ($is_validate === true) {
+                       $this->getCallbackClient()->show('db_test_result_ok');
+                       $this->getCallbackClient()->hide('db_test_result_err');
+                       $this->getCallbackClient()->hide($this->DbTestResultErr);
+               } else {
+                       $this->getCallbackClient()->hide('db_test_result_ok');
+                       $this->getCallbackClient()->show('db_test_result_err');
+                       $this->getCallbackClient()->show($this->DbTestResultErr);
+               }
        }
 
        public function connectionBconsoleTest($sender, $param) {
@@ -438,8 +387,15 @@ class APIInstallWizard extends BaculumAPIPage {
                if (!$is_validate) {
                        $this->BconsoleTestResultErr->Text = $result->output;
                }
-               $this->BconsoleTestResultOk->Display = ($is_validate === true) ? 'Dynamic' : 'None';
-               $this->BconsoleTestResultErr->Display = ($is_validate === false) ? 'Dynamic' : 'None';
+               if ($is_validate === true) {
+                       $this->getCallbackClient()->show('bconsole_test_result_ok');
+                       $this->getCallbackClient()->hide('bconsole_test_result_err');
+                       $this->getCallbackClient()->hide($this->BconsoleTestResultErr);
+               } else {
+                       $this->getCallbackClient()->hide('bconsole_test_result_ok');
+                       $this->getCallbackClient()->show('bconsole_test_result_err');
+                       $this->getCallbackClient()->show($this->BconsoleTestResultErr);
+               }
        }
 
        public function testJSONToolsCfg($sender, $param) {
@@ -502,23 +458,5 @@ class APIInstallWizard extends BaculumAPIPage {
                }
                $param->setIsValid($valid);
        }
-
-       public function testExecActionCommand($sender, $param) {
-               $action = $param->CommandParameter;
-               $cmd = '';
-               switch ($action) {
-                       case 'dir_start': $cmd = $this->DirStartAction->Text; break;
-                       case 'dir_stop': $cmd = $this->DirStopAction->Text; break;
-                       case 'dir_restart': $cmd = $this->DirRestartAction->Text; break;
-                       case 'sd_start': $cmd = $this->SdStartAction->Text; break;
-                       case 'sd_stop': $cmd = $this->SdStopAction->Text; break;
-                       case 'sd_restart': $cmd = $this->SdRestartAction->Text; break;
-                       case 'fd_start': $cmd = $this->FdStartAction->Text; break;
-                       case 'fd_stop': $cmd = $this->FdStopAction->Text; break;
-                       case 'fd_restart': $cmd = $this->FdRestartAction->Text; break;
-               };
-               $result = $this->getModule('comp_actions')->execCommand($cmd, $this->ActionsUseSudo->Checked);
-               $this->getCallbackClient()->callClientFunction('set_action_command_output', array($action, (array)$result));
-       }
 }
 ?>
diff --git a/gui/baculum/protected/API/Pages/Panel/APIOAuth2Clients.php b/gui/baculum/protected/API/Pages/Panel/APIOAuth2Clients.php
new file mode 100644 (file)
index 0000000..1535777
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.API.Class.BaculumAPIPage');
+
+/**
+ * API OAuth2 clients page.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Panel
+ * @package Baculum API
+ */
+class APIOAuth2Clients extends BaculumAPIPage {
+
+       public function onInit($param) {
+               parent::onInit($param);
+
+               $config = $this->getModule('api_config')->getConfig();
+               if(count($config) === 0) {
+                       // Config doesn't exist, go to wizard
+                       $this->goToPage('APIInstallWizard');
+                       return;
+               } elseif (!$this->IsCallback) {
+                       $this->loadOAuth2Clients(null, null);
+               }
+       }
+
+       public function loadOAuth2Clients($sender, $param) {
+               $oauth2_cfg = $this->getModule('oauth2_config')->getConfig();
+               $clients = array_values($oauth2_cfg);
+               $this->getCallbackClient()->callClientFunction(
+                       'oAPIOAuth2Clients.load_oauth2_clients_cb',
+                       [$clients]
+               );
+               $this->hideOAuth2ClientWindow($sender);
+       }
+
+       public function cancelOAuth2ClientWindow($sender, $param) {
+               $this->hideOAuth2ClientWindow($sender);
+       }
+
+       private function hideOAuth2ClientWindow($sender) {
+               if (is_object($sender)) {
+                       if ($sender->ID === 'NewOAuth2Client') {
+                               $this->getCallbackClient()->callClientFunction(
+                                       'oAPIOAuth2Clients.show_new_client_window',
+                                       [false]
+                               );
+                       } elseif ($sender->ID === 'EditOAuth2Client') {
+                               $this->getCallbackClient()->callClientFunction(
+                                       'oAPIOAuth2Clients.show_edit_client_window',
+                                       [false]
+                               );
+                       }
+               }
+       }
+
+       public function deleteOAuth2Client($sender, $param) {
+               $config = $this->getModule('oauth2_config');
+               $clients = $config->getConfig();
+               $client_id = $param->getCallbackParameter();
+               if (key_exists($client_id, $clients)) {
+                       unset($clients[$client_id]);
+               }
+               $config->setConfig($clients);
+               $this->loadOAuth2Clients(null, null);
+       }
+}
+?>
diff --git a/gui/baculum/protected/API/Pages/Panel/APIOAuth2Clients.tpl b/gui/baculum/protected/API/Pages/Panel/APIOAuth2Clients.tpl
new file mode 100644 (file)
index 0000000..4a7e5a8
--- /dev/null
@@ -0,0 +1,352 @@
+<%@ MasterClass="Application.API.Layouts.Main" Theme="Baculum-v2"%>
+<com:TContent ID="Main">
+       <header class="w3-container w3-block">
+               <h5>
+                       <i class="fas fa-user-shield"></i> <%[ OAuth2 clients ]%>
+               </h5>
+       </header>
+       <div class="w3-container">
+               <a href="javascript:void(0)" class="w3-button w3-green w3-margin-bottom" onclick="oAPIOAuth2Clients.show_new_client_window(true);">
+                       <i class="fas fa-plus"></i> &nbsp;<%[ Add OAuth2 client ]%>
+               </a>
+               <table id="oauth2_client_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+                       <thead>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Name ]%></th>
+                                       <th><%[ Client ID ]%></th>
+                                       <th><%[ Redirect URI ]%></th>
+                                       <th><%[ Actions ]%></th>
+                               </tr>
+                       </thead>
+                       <tbody id="oauth2_client_list_body"></tbody>
+                       <tfoot>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Name ]%></th>
+                                       <th><%[ Client ID ]%></th>
+                                       <th><%[ Redirect URI ]%></th>
+                                       <th><%[ Actions ]%></th>
+                               </tr>
+                       </tfoot>
+               </table>
+       </div>
+<script>
+var oOAuth2ClientList = {
+       ids: {
+               oauth2_client_list: 'oauth2_client_list',
+               oauth2_client_list_body: 'oauth2_client_list_body'
+       },
+       table: null,
+       data: [],
+       init: function() {
+               if (!this.table) {
+                       this.set_table();
+               } else {
+                       var page = this.table.page();
+                       this.table.clear().rows.add(this.data).draw();
+                       this.table.page(page).draw(false);
+               }
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.oauth2_client_list).DataTable({
+                       data: this.data,
+                       deferRender: true,
+                       dom: 'lBfrtip',
+                       stateSave: true,
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
+                       columns: [
+                               {
+                                       className: 'details-control',
+                                       orderable: false,
+                                       data: null,
+                                       defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+                               },
+                               {data: 'name'},
+                               {data: 'client_id'},
+                               {data: 'redirect_uri'},
+                               {
+                                       data: 'client_id',
+                                       render: function(data, type, row) {
+                                               var span = document.createElement('SPAN');
+                                               span.className = 'w3-right';
+
+                                               var chpwd_btn = document.createElement('BUTTON');
+                                               chpwd_btn.className = 'w3-button w3-green';
+                                               chpwd_btn.type = 'button';
+                                               var i = document.createElement('I');
+                                               i.className = 'fas fa-edit';
+                                               var label = document.createTextNode(' <%[ Edit ]%>');
+                                               chpwd_btn.appendChild(i);
+                                               chpwd_btn.innerHTML += '&nbsp';
+                                               chpwd_btn.appendChild(label);
+                                               chpwd_btn.setAttribute('onclick', 'oAPIOAuth2Clients.edit_client("' + data + '")');
+
+                                               var del_btn = document.createElement('BUTTON');
+                                               del_btn.className = 'w3-button w3-red w3-margin-left';
+                                               del_btn.type = 'button';
+                                               var i = document.createElement('I');
+                                               i.className = 'fas fa-trash-alt';
+                                               var label = document.createTextNode(' <%[ Delete ]%>');
+                                               del_btn.appendChild(i);
+                                               del_btn.innerHTML += '&nbsp';
+                                               del_btn.appendChild(label);
+                                               del_btn.setAttribute('onclick', 'oAPIOAuth2Clients.delete_client("' + data + '")');
+
+                                               span.appendChild(chpwd_btn);
+                                               span.appendChild(del_btn);
+
+                                               return span.outerHTML;
+                                       }
+                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: 'control',
+                               orderable: false,
+                               targets: 0
+                       },
+                       {
+                               className: "dt-center",
+                               targets: [ 4 ]
+                       }],
+                       order: [1, 'asc'],
+               });
+       }
+};
+</script>
+       <div id="new_oauth2_client_window" class="w3-modal">
+               <div class="w3-modal-content w3-animate-top w3-card-4">
+                       <header class="w3-container w3-teal">
+                               <span onclick="document.getElementById('new_oauth2_client_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                               <h2><%[ Add client ]%></h2>
+                       </header>
+                       <div class="w3-container w3-margin-left w3-margin-right w3-text-teal">
+                               <com:Application.Common.Portlets.NewAuthClient
+                                       ID="NewOAuth2Client"
+                                       Mode="add"
+                                       AuthType="oauth2"
+                                       OnSuccess="loadOAuth2Clients"
+                                       OnCancel="cancelOAuth2ClientWindow"
+                               />
+                       </div>
+               </div>
+       </div>
+       <div id="edit_oauth2_client_window" class="w3-modal">
+               <div class="w3-modal-content w3-animate-top w3-card-4">
+                       <header class="w3-container w3-teal">
+                               <span onclick="document.getElementById('edit_oauth2_client_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                               <h2><%[ Edit client ]%></h2>
+                       </header>
+                       <div class="w3-container w3-margin-left w3-margin-right w3-text-teal">
+                               <com:Application.Common.Portlets.NewAuthClient
+                                       ID="EditOAuth2Client"
+                                       Mode="edit"
+                                       AuthType="oauth2"
+                                       OnSuccess="loadOAuth2Clients"
+                                       OnCancel="cancelOAuth2ClientWindow"
+                               />
+                       </div>
+               </div>
+       </div>
+<com:TCallback ID="LoadClients" OnCallback="loadOAuth2Clients" />
+<com:TCallback ID="DeleteClient" OnCallback="deleteOAuth2Client" />
+<script>
+var oAPIOAuth2Clients = {
+       ids: {
+               new_user_window: 'new_oauth2_client_window',
+               new_oauth2_client_id: '<%=$this->NewOAuth2Client->APIOAuth2ClientId->ClientID%>',
+               edit_user_window: 'edit_oauth2_client_window',
+               edit_oauth2_client_secret: '<%=$this->EditOAuth2Client->APIOAuth2ClientSecret->ClientID%>'
+       },
+       new_obj: <%=$this->NewOAuth2Client->ClientID%>oNewAuthClient,
+       edit_obj: <%=$this->EditOAuth2Client->ClientID%>oNewAuthClient,
+       init: function() {
+               this.load_oauth2_clients();
+       },
+       show_new_client_window: function(show) {
+               oAPIOAuth2Clients.new_obj.hide_errors();
+               oAPIOAuth2Clients.new_obj.clear_oauth2_fields();
+               var win = document.getElementById(oAPIOAuth2Clients.ids.new_user_window);
+               if (show) {
+                       win.style.display = 'block';
+               } else {
+                       win.style.display = 'none';
+               }
+               document.getElementById(oAPIOAuth2Clients.ids.new_oauth2_client_id).focus();
+       },
+       show_edit_client_window: function(show) {
+               oAPIOAuth2Clients.edit_obj.hide_errors();
+               var win = document.getElementById(oAPIOAuth2Clients.ids.edit_user_window);
+               if (show) {
+                       win.style.display = 'block';
+               } else {
+                       win.style.display = 'none';
+               }
+               document.getElementById(oAPIOAuth2Clients.ids.edit_oauth2_client_secret).focus();
+       },
+       load_oauth2_clients: function() {
+               var cb = <%=$this->LoadClients->ActiveControl->Javascript%>;
+               cb.dispatch();
+       },
+       load_oauth2_clients_cb: function(clients) {
+               oOAuth2ClientList.data = clients;
+               oOAuth2ClientList.init();
+       },
+       get_client_props: function(client_id) {
+               var clients_len = oOAuth2ClientList.data.length;
+               var client = {};
+               for (var i = 0; i < clients_len; i++) {
+                       if (oOAuth2ClientList.data[i].client_id === client_id) {
+                               client = oOAuth2ClientList.data[i];
+                               break;
+                       }
+               }
+               return client;
+       },
+       edit_client: function(client_id) {
+               this.edit_obj.clear_oauth2_fields();
+               var props = this.get_client_props(client_id);
+               this.edit_obj.set_oauth2_props(props);
+               this.show_edit_client_window(true);
+       },
+       delete_client: function(username) {
+               if (!confirm('<%[ Are you sure? ]%>')) {
+                       return false;
+               }
+               var cb = <%=$this->DeleteClient->ActiveControl->Javascript%>;
+               cb.setCallbackParameter(username);
+               cb.dispatch();
+       }
+};
+$(function() {
+       oAPIOAuth2Clients.init();
+});
+</script>
+       <com:TJuiDialog
+               ID="APIOAuth2EditPopup"
+               Options.Title="<%[ Edit OAuth2 client parameters ]%>"
+               Options.AutoOpen="False"
+               Options.Width="700px"
+       >
+               <com:TPanel DefaultButton="APIOAuth2SaveBtn">
+               <div class="line">
+                       <div class="text"><com:TLabel ForControl="APIOAuth2ClientId" Text="<%[ OAuth2 Client ID: ]%>" /></div>
+                       <div class="field">
+                               <com:TActiveTextBox
+                                       ID="APIOAuth2ClientId"
+                                       CssClass="textbox"
+                                       ReadOnly="true"
+                               />
+                       </div>
+               </div>
+               <div class="line">
+                       <div class="text"><com:TLabel ForControl="APIOAuth2ClientSecret" Text="<%[ OAuth2 Client Secret: ]%>" /></div>
+                       <div class="field">
+                               <com:TActiveTextBox
+                                       ID="APIOAuth2ClientSecret"
+                                       CssClass="textbox"
+                                       CausesValidation="false"
+                                       MaxLength="50"
+                               />
+                               <com:TRequiredFieldValidator
+                                       CssClass="validator-block"
+                                       Display="Dynamic"
+                                       ControlCssClass="invalidate"
+                                       ControlToValidate="APIOAuth2ClientSecret"
+                                       ValidationGroup="APIOAuth2Edit"
+                                       Text="<%[ Please enter Client Secret. ]%>"
+                               />
+                               <com:TRegularExpressionValidator
+                                       CssClass="validator-block"
+                                       Display="Dynamic"
+                                       ControlCssClass="invalidate"
+                                       ControlToValidate="APIOAuth2ClientSecret"
+                                       RegularExpression="<%=OAuth2::CLIENT_SECRET_PATTERN%>"
+                                       ValidationGroup="APIOAuth2Edit"
+                                       Text="<%[ Invalid Client Secret value. Client Secret may contain any character that is not a whitespace character. ]%>"
+                               />
+                               <a href="javascript:void(0)" onclick="document.getElementById('<%=$this->APIOAuth2ClientSecret->ClientID%>').value = get_random_string('ABCDEFabcdef0123456789', 40); return false;"><%[ generate ]%></a>
+                       </div>
+               </div>
+               <div class="line">
+                       <div class="text"><com:TLabel ForControl="APIOAuth2RedirectURI" Text="<%[ OAuth2 Redirect URI (example: https://baculumgui:9095/web/redirect): ]%>" /></div>
+                       <div class="field">
+                               <com:TActiveTextBox
+                                       ID="APIOAuth2RedirectURI"
+                                       CssClass="textbox"
+                                       CausesValidation="false"
+                               />
+                               <com:TRequiredFieldValidator
+                                       CssClass="validator-block"
+                                       Display="Dynamic"
+                                       ControlCssClass="invalidate"
+                                       ControlToValidate="APIOAuth2RedirectURI"
+                                       ValidationGroup="APIOAuth2Edit"
+                                       Text="<%[ Please enter Redirect URI. ]%>"
+                               />
+                       </div>
+               </div>
+               <div class="line">
+                       <div class="text"><com:TLabel ForControl="APIOAuth2Scope" Text="<%[ OAuth2 scopes (space separated): ]%>" /></div>
+                       <div class="field">
+                               <com:TActiveTextBox
+                                       ID="APIOAuth2Scope"
+                                       CssClass="textbox"
+                                       CausesValidation="false"
+                                       TextMode="MultiLine"
+                               />
+                               <a href="javascript:void(0)" onclick="set_scopes('<%=$this->APIOAuth2Scope->ClientID%>'); return false;" style="vertical-align: top"><%[ set all scopes ]%></a>
+                               <com:TRequiredFieldValidator
+                                       CssClass="validator-block"
+                                       Display="Dynamic"
+                                       ControlCssClass="invalidate"
+                                       ControlToValidate="APIOAuth2Scope"
+                                       ValidationGroup="APIOAuth2Edit"
+                                       Text="<%[ Please enter OAuth2 scopes. ]%>"
+                               />
+                       </div>
+               </div>
+               <div class="line">
+                       <div class="text"><com:TLabel ForControl="APIOAuth2BconsoleCfgPath" Text="<%[ Dedicated Bconsole config file path: ]%>" /></div>
+                       <div class="field">
+                               <com:TActiveTextBox
+                                       ID="APIOAuth2BconsoleCfgPath"
+                                       CssClass="textbox"
+                                       CausesValidation="false"
+                               /> <%[ (optional) ]%>
+                       </div>
+               </div>
+               <div class="line">
+                       <div class="text"><com:TLabel ForControl="APIOAuth2Name" Text="<%[ Short name: ]%>" /></div>
+                       <div class="field">
+                               <com:TActiveTextBox
+                                       ID="APIOAuth2Name"
+                                       CssClass="textbox"
+                                       CausesValidation="false"
+                               /> <%[ (optional) ]%>
+                       </div>
+               </div>
+               <div class="center">
+                       <com:BButton
+                               Text="<%[ Cancel ]%>"
+                               CausesValidation="false"
+                               Attributes.onclick="$('#<%=$this->APIOAuth2EditPopup->ClientID%>').dialog('close'); return false;"
+                       />
+                       <com:BActiveButton
+                               ID="APIOAuth2SaveBtn"
+                               ValidationGroup="APIOAuth2Edit"
+                               OnCommand="TemplateControl.saveOAuth2Item"
+                               Text="<%[ Save ]%>"
+                       >
+                       </com:BActiveButton>
+               </div>
+               </com:TPanel>
+       </com:TJuiDialog>
+</com:TContent>
diff --git a/gui/baculum/protected/API/Pages/Panel/APISettings.page b/gui/baculum/protected/API/Pages/Panel/APISettings.page
new file mode 100644 (file)
index 0000000..e4dc9cf
--- /dev/null
@@ -0,0 +1,1401 @@
+<%@ MasterClass="Application.API.Layouts.Main" Theme="Baculum-v2"%>
+<com:TContent ID="Main">
+       <header class="w3-container w3-block">
+               <h5>
+                       <i class="fas fa-wrench"></i> <%[ API settings ]%>
+               </h5>
+       </header>
+       <div class="w3-bar w3-green w3-margin-bottom">
+               <button type="button" id="btn_settings_general" class="w3-bar-item w3-button tab_btn w3-grey" onclick="W3Tabs.open(this.id, 'settings_general');"><%[ General ]%></button>
+               <button type="button" id="btn_settings_catalog" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'settings_catalog');"><%[ Catalog ]%></button>
+               <button type="button" id="btn_settings_console" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'settings_console');"><%[ Console ]%></button>
+               <button type="button" id="btn_settings_config" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'settings_config');"><%[ Config ]%></button>
+               <button type="button" id="btn_settings_actions" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'settings_actions');"><%[ Actions ]%></button>
+               <button type="button" id="btn_settings_authentication" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'settings_authentication');"><%[ Authentication ]%></button>
+       </div>
+       <div class="w3-container tab_item" id="settings_general">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="GeneralLang" Text="<%[ Language: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
+                               <com:TActiveDropDownList
+                                       ID="GeneralLang"
+                                       CssClass="w3-select w3-border"
+                                       Width="150px"
+                                       CausesValidation="false"
+                                       AutoPostBack="false"
+                               >
+                                       <com:TListItem Value="en" Text="English" />
+                                       <com:TListItem Value="pl" Text="Polish" />
+                                       <com:TListItem Value="pt" Text="Portuguese" />
+                                       <com:TListItem Value="ru" Text="Russian" />
+                               </com:TActiveDropDownList>
+                       </div>
+               </div>
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="GeneralDebug" Text="<%[ Debug: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
+                               <com:TActiveCheckBox
+                                       ID="GeneralDebug"
+                                       CssClass="w3-check"
+                               />
+                       </div>
+               </div>
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-center">
+                               <com:TActiveLinkButton
+                                       ID="GeneralSave"
+                                       CssClass="w3-button w3-green"
+                                       OnCallback="saveGeneral"
+                               >
+                                       <i class="fas fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TActiveLinkButton>
+                       </div>
+               </div>
+       </div>
+       <div class="w3-container tab_item" id="settings_catalog" style="display: none">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="DBEnabled" Text="<%[ Enabled: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
+                               <com:TActiveCheckBox
+                                       ID="DBEnabled"
+                                       CssClass="w3-check"
+                                       Attributes.onclick="$('#db_container').slideToggle('fast')"
+                               />
+                       </div>
+               </div>
+               <div id="db_container" style="display: <%=$this->DBEnabled->Checked ? 'block' : 'none'%>">
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter"><com:TLabel ForControl="DBType" Text="<%[ Database type: ]%>" /></div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TActiveDropDownList
+                                               ID="DBType"
+                                               CssClass="w3-select w3-border"
+                                               Width="170px"
+                                               OnTextChanged="setDBType"
+                                               CausesValidation="false"
+                                       >
+                                               <com:TListItem Value="none" Text="<%[ select database ]%>" />
+                                               <com:TListItem Value="pgsql" Text="PostgreSQL" />
+                                               <com:TListItem Value="mysql" Text="MySQL" />
+                                               <com:TListItem Value="sqlite" Text="SQLite" />
+                                       </com:TActiveDropDownList>
+                                       <com:TCompareValidator
+                                               Display="Dynamic"
+                                               ControlToValidate="DBType"
+                                               DataType="String"
+                                               ValueToCompare="none"
+                                               Operator="NotEqual"
+                                               ValidationGroup="DbGroup"
+                                               Text="<%[ Please select database type. ]%>"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter"><com:TLabel ForControl="DBName" Text="<%[ Database name: ]%>" /></div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TTextBox
+                                               ID="DBName"
+                                               CssClass="w3-input w3-border"
+                                               Width="370px"
+                                       />
+                                       <com:TRequiredFieldValidator
+                                               Display="Dynamic"
+                                               ControlToValidate="DBName"
+                                               ValidationGroup="DbGroup"
+                                               Text="<%[ Please enter database name. ]%>"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter"><com:TLabel ForControl="DBLogin" Text="<%[ Login: ]%>" /></div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TActiveTextBox
+                                               ID="DBLogin"
+                                               CssClass="w3-input w3-border"
+                                               Width="370px"
+                                       />
+                                       <com:TRequiredFieldValidator
+                                               ID="LoginValidator"
+                                               Display="Dynamic"
+                                               ControlToValidate="DBLogin"
+                                               ValidationGroup="DbGroup"
+                                               Text="<%[ Please enter login. ]%>"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter"><com:TLabel ForControl="DBPassword" Text="<%[ Password: ]%>" /></div>
+                               <div class="w3-col w3-threequarter"><com:TActiveTextBox
+                                       ID="DBPassword"
+                                       CssClass="w3-input w3-border"
+                                       TextMode="Password"
+                                       PersistPassword="true"
+                                       Width="370px"
+                                /></div>
+                       </div>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter"><com:TLabel ForControl="DBAddress" Text="<%[ IP address (or hostname): ]%>" /></div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TActiveTextBox
+                                               ID="DBAddress"
+                                               CssClass="w3-input w3-border"
+                                               Width="370px"
+                                       />
+                                       <com:TRequiredFieldValidator
+                                               ID="IPValidator"
+                                               Display="Dynamic"
+                                               ControlToValidate="DBAddress"
+                                               ValidationGroup="DbGroup"
+                                               Text="<%[ Please enter IP address or hostname. ]%>"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter"><com:TLabel ForControl="DBPort" Text="<%[ Port: ]%>" /></div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TActiveTextBox
+                                               ID="DBPort"
+                                               CssClass="w3-input w3-border"
+                                               Width="100px"
+                                               MaxLength="5"
+                                               Enabled="false"
+                                       />
+                                       <com:TRequiredFieldValidator
+                                               ID="PortValidator"
+                                               Display="Dynamic"
+                                               ControlToValidate="DBPort"
+                                               ValidationGroup="DbGroup"
+                                               Text="<%[ Please enter database port. ]%>"
+                                       />
+                               </div>
+                       </div>
+                       <com:TActivePanel
+                               ID="DBPathField"
+                               CssClass="w3-row w3-section"
+                               Display="<%=!$this->IsCallBack && $this->getPage()->config['db']['type'] == 'sqlite' ? 'Fixed' : $this->DBPathField->Display%>">
+                               <div class="w3-col w3-quarter"><com:TLabel ForControl="DBPath" Text="<%[ SQLite database path: ]%>" /></div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TActiveTextBox
+                                               ID="DBPath"
+                                               CssClass="w3-input w3-border"
+                                               Enabled="false"
+                                               Width="370px"
+                                       />
+                                       <com:TRequiredFieldValidator
+                                               ID="DBPathValidator"
+                                               Display="Dynamic"
+                                               ControlToValidate="DBPath"
+                                               ValidationGroup="DbGroup"
+                                               Text="<%[ Please enter database path. ]%>"
+                                       />
+                               </div>
+                       </com:TActivePanel>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter" style="vertical-align: top"><com:TLabel ForControl="ConnectionTest" Text="<%[ Connection test: ]%>" CssClass="test_label" /></div>
+                               <div class="w3-col w3-threequarter">
+                                       <table>
+                                               <tr>
+                                                       <td align="center" valign="middle">
+                                                               <com:TActiveLinkButton
+                                                                       ID="ConnectionTest"
+                                                                       CssClass="w3-button w3-green"
+                                                                       CausesValidation="false"
+                                                                       OnCallback="connectionDBTest"
+                                                               >
+                                                                       <prop:ClientSide.OnLoading>
+                                                                               $('#db_test_result_ok').hide();
+                                                                               $('#db_test_result_err').hide();
+                                                                               $('#<%=$this->DbTestResultErr->ClientID%>').hide();
+                                                                               $('#db_test_loader').show();
+                                                                       </prop:ClientSide.OnLoading>
+                                                                       <prop:ClientSide.OnComplete>
+                                                                               $('#db_test_loader').hide();
+                                                                       </prop:ClientSide.OnComplete>
+                                                                       <i class="fas fa-play"></i> &nbsp;<%[ test ]%>
+                                                               </com:TActiveLinkButton>
+                                                       </td>
+                                                       <td style="padding-left: 10px">
+                                                               <span id="db_test_loader" style="display: none">
+                                                                       <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                               </span>
+                                                               <span id="db_test_result_ok" class="w3-text-green" style="display: none">
+                                                                       <i class="fas fa-check"></i> &nbsp;<%[ OK ]%>
+                                                               </span>
+                                                               <span id="db_test_result_err" class="w3-text-red" style="display: none">
+                                                                       <i class="fas fa-exclamation-circle"></i> &nbsp;
+                                                               </span>
+                                                               <com:TActiveLabel ID="DbTestResultErr" CssClass="w3-text-red" Display="None"><%[ Connection error ]%></com:TActiveLabel>
+                                                       </td>
+                                               </tr>
+                                       </table>
+                               </div>
+                       </div>
+               </div>
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-center">
+                               <com:TActiveLinkButton
+                                       ID="CatalogSave"
+                                       CssClass="w3-button w3-green"
+                                       OnCallback="saveCatalog"
+                                       ValidationGroup="DbGroup"
+                                       CausesValidation="true"
+                               >
+                                       <i class="fas fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TActiveLinkButton>
+                       </div>
+               </div>
+       </div>
+       <div class="w3-container tab_item" id="settings_console" style="display: none">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="BconsoleEnabled" Text="<%[ Enabled: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
+                               <com:TActiveCheckBox
+                                       ID="BconsoleEnabled"
+                                       CssClass="w3-check"
+                                       Attributes.onclick="$('#bconsole_container').slideToggle('fast')"
+                               />
+                       </div>
+               </div>
+               <div id="bconsole_container" style="display: <%=$this->BconsoleEnabled->Checked ? 'block' : 'none'%>">
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter">
+                                       <com:TLabel
+                                               ForControl="BconsolePath"
+                                               Text="<%[ Bconsole binary file path: ]%>"
+                                       />
+                               </div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TTextBox
+                                               ID="BconsolePath"
+                                               CssClass="w3-input w3-border"
+                                               CausesValidation="false"
+                                               ValidationGroup="BconsoleGroup"
+                                               Width="370px"
+                                       />
+                                       <com:TRequiredFieldValidator
+                                               Display="Dynamic"
+                                               ControlToValidate="BconsolePath"
+                                               Text="<%[ Please enter bconsole path. ]%>"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter">
+                                       <com:TLabel
+                                               ForControl="BconsoleConfigPath"
+                                               Text="<%[ Bconsole admin config file path: ]%>"
+                                       />
+                               </div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TTextBox
+                                               ID="BconsoleConfigPath"
+                                               CssClass="w3-input w3-border"
+                                               CausesValidation="false"
+                                               ValidationGroup="BconsoleGroup"
+                                               Width="370px"
+                                       />
+                                       <com:TRequiredFieldValidator
+                                               Display="Dynamic"
+                                               ControlToValidate="BconsoleConfigPath"
+                                               Text="<%[ Please enter bconsole config file path. ]%>"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter">
+                                       <com:TLabel
+                                               ForControl="BconsoleUseSudo"
+                                               Text="<%[ Use sudo: ]%>"
+                                       />
+                               </div>
+                               <div class="w3-col w3-threequarter">
+                                       <com:TCheckBox
+                                               ID="BconsoleUseSudo"
+                                               CssClass="w3-check"
+                                       /> &nbsp;<a href="javascript:void(0)" onclick="get_sudo_config('bconsole');"><%[ Get sudo configuration ]%></a>
+                               </div>
+                       </div>
+                       <div class="w3-row w3-section">
+                               <div class="w3-col w3-quarter">
+                                       <com:TLabel
+                                               ForControl="BconsoleConnectionTest"
+                                               Text="<%[ Bconsole connection test: ]%>"
+                                       /></div>
+                               <div class="w3-col w3-threequarter">
+                                       <table>
+                                               <tr>
+                                                       <td>
+                                                               <com:TActiveLinkButton
+                                                                       ID="BconsoleConnectionTest"
+                                                                       CssClass="w3-button w3-green"
+                                                                       CausesValidation="false"
+                                                                       OnCallback="connectionBconsoleTest"
+                                                               >
+                                                                       <prop:ClientSide.OnLoading>
+                                                                               $('#bconsole_test_result_ok').hide();
+                                                                               $('#bconsole_test_result_err').hide();
+                                                                               $('#<%=$this->BconsoleTestResultErr->ClientID%>').hide();
+                                                                               $('#bconsole_test_loader').show();
+                                                                       </prop:ClientSide.OnLoading>
+                                                                       <prop:ClientSide.OnComplete>
+                                                                               $('#bconsole_test_loader').hide();
+                                                                       </prop:ClientSide.OnComplete>
+                                                                       <i class="fas fa-play"></i> &nbsp;<%[ test ]%>
+                                                               </com:TActiveLinkButton>
+                                                       </td>
+                                                       <td style="padding-left: 10px">
+                                                               <span id="bconsole_test_loader" style="display: none">
+                                                                       <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                               </span>
+                                                               <span id="bconsole_test_result_ok" class="w3-text-green" style="display: none">
+                                                                       <i class="fas fa-check"></i> &nbsp;<%[ OK ]%>
+                                                               </span>
+                                                               <span id="bconsole_test_result_err" class="w3-text-red" style="display: none">
+                                                                       <i class="fas fa-exclamation-circle"></i> &nbsp;
+                                                               </span>
+                                                               <com:TActiveLabel ID="BconsoleTestResultErr" CssClass="w3-text-red" Display="None"><%[ Connection error ]%></com:TActiveLabel>
+                                                       </td>
+                                               </tr>
+                                       </table>
+                               </div>
+                       </div>
+               </div>
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-center">
+                               <com:TActiveLinkButton
+                                       ID="BconsoleSave"
+                                       CssClass="w3-button w3-green"
+                                       OnCallback="saveBconsole"
+                                       ValidationGroup="BconsoleGroup"
+                                       CausesValidation="true"
+                               >
+                                       <i class="fas fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TActiveLinkButton>
+                       </div>
+               </div>
+       </div>
+       <div class="w3-container tab_item" id="settings_config" style="display: none">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="ConfigEnabled" Text="<%[ Enabled: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
+                               <com:TActiveCheckBox
+                                       ID="ConfigEnabled"
+                                       CssClass="w3-check"
+                                       Attributes.onclick="$('#config_container').slideToggle('fast')"
+                               />
+                       </div>
+               </div>
+               <div id="config_container" style="display: <%=$this->ConfigEnabled->Checked ? 'block' : 'none'%>">
+                       <fieldset>
+                               <legend><%[ General configuration ]%></legend>
+                               <div class="w3-row w3-section" title="<%[ In this directory Baculum API saves temporarily Bacula configuration files (mainly for validation purposes) just before they are written as real Bacula configuration files. ]%>">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="BConfigDir"
+                                                       Text="<%[ Baculum working directory for Bacula config: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="BConfigDir"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               /> &nbsp;
+                                               <span class="config_test_loader" style="display: none">
+                                                       <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                               </span>
+                                               <com:TActiveLabel
+                                                       ID="BConfigDirTestOk"
+                                                       Display="None"
+                                                       CssClass="w3-text-green"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                               </com:TActiveLabel>
+                                               <com:TActiveLabel
+                                                       ID="BConfigDirTestErr"
+                                                       Display="None"
+                                                       CssClass="w3-text-red"
+                                                       EnableViewState="false"
+                                               >
+                                                       <!--i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i--> &nbsp;<%[ Error ]%>
+                                               </com:TActiveLabel>
+                                               <com:TRequiredFieldValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="BConfigDir"
+                                                       Display="Dynamic"
+                                                       Text="<%[ Directory path field is required. ]%>"
+                                               />
+                                               <com:TActiveCustomValidator
+                                                       ID="BConfigDirWritableTest"
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="BConfigDir"
+                                                       Text="<%[ Provided directory path is not writable by web server. ]%>"
+                                                       Display="Dynamic"
+                                                       OnServerValidate="testConfigDir"
+                                               />
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="BJSONUseSudo"
+                                                       Text="<%[ Use sudo: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TCheckBox
+                                                       ID="BJSONUseSudo"
+                                                       CssClass="w3-check"
+                                               /> &nbsp;<a href="javascript:void(0)" onclick="get_sudo_config('config');"><%[ Get sudo configuration ]%></a>
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend>Director</legend>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="BDirJSONPath"
+                                                       Text="<%[ bdirjson binary file path: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="BDirJSONPath"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               /> &nbsp;
+                                               <span class="config_test_loader" style="display: none">
+                                                       <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                               </span>
+                                               <com:TActiveLabel
+                                                       ID="BDirJSONPathTestOk"
+                                                       Display="None"
+                                                       CssClass="w3-text-green"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                               </com:TActiveLabel>
+                                               <com:TCustomValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="BDirJSONPath"
+                                                       ClientValidationFunction="bjsontools_validator"
+                                                       Display="Dynamic"
+                                                       Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                               />
+                                               <com:TActiveLabel
+                                                       ID="BDirJSONPathTestErr"
+                                                       Display="None"
+                                                       CssClass="w3-text-red"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Connection error ]%>
+                                               </com:TActiveLabel>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="DirCfgPath"
+                                                       Text="<%[ Main Director config file path (usually bacula-dir.conf): ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="DirCfgPath"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TCustomValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="DirCfgPath"
+                                                       ClientValidationFunction="bjsontools_validator"
+                                                       Display="Dynamic"
+                                                       Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                               />
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend>Storage Daemon</legend>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="BSdJSONPath"
+                                                       Text="<%[ bsdjson binary file path: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="BSdJSONPath"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               /> &nbsp;
+                                               <span class="config_test_loader" style="display: none">
+                                                       <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                               </span>
+                                               <com:TCustomValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="BSdJSONPath"
+                                                       ClientValidationFunction="bjsontools_validator"
+                                                       Display="Dynamic"
+                                                       Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                               />
+                                               <com:TActiveLabel
+                                                       ID="BSdJSONPathTestOk"
+                                                       Display="None"
+                                                       CssClass="w3-text-green"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                               </com:TActiveLabel>
+                                               <com:TActiveLabel
+                                                       ID="BSdJSONPathTestErr"
+                                                       Display="None"
+                                                       CssClass="w3-text-red"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Connection error ]%>
+                                               </com:TActiveLabel>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="SdCfgPath"
+                                                       Text="<%[ Main Storage Daemon config file path (usually bacula-sd.conf): ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="SdCfgPath"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TCustomValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="SdCfgPath"
+                                                       ClientValidationFunction="bjsontools_validator"
+                                                       Display="Dynamic"
+                                                       Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                               />
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend>File Daemon/Client</legend>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="BFdJSONPath"
+                                                       Text="<%[ bfdjson binary file path: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="BFdJSONPath"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               /> &nbsp;
+                                               <com:TCustomValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="BFdJSONPath"
+                                                       ClientValidationFunction="bjsontools_validator"
+                                                       Display="Dynamic"
+                                                       Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                               />
+                                               <span class="config_test_loader" style="display: none">
+                                                       <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                               </span>
+                                               <com:TActiveLabel
+                                                       ID="BFdJSONPathTestOk"
+                                                       Display="None"
+                                                       CssClass="w3-text-green"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                               </com:TActiveLabel>
+                                               <com:TActiveLabel
+                                                       ID="BFdJSONPathTestErr"
+                                                       Display="None"
+                                                       CssClass="w3-text-red"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Connection error ]%>
+                                               </com:TActiveLabel>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="FdCfgPath"
+                                                       Text="<%[ Main File Daemon config file path (usually bacula-fd.conf): ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="FdCfgPath"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TCustomValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="FdCfgPath"
+                                                       ClientValidationFunction="bjsontools_validator"
+                                                       Display="Dynamic"
+                                                       Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                               />
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend>Bconsole</legend>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="BBconsJSONPath"
+                                                       Text="<%[ bbconsjson binary file path: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TCustomValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="BBconsJSONPath"
+                                                       ClientValidationFunction="bjsontools_validator"
+                                                       Display="Dynamic"
+                                                       Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                               />
+                                               <com:TTextBox
+                                                       ID="BBconsJSONPath"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               /> &nbsp;
+                                               <span class="config_test_loader" style="display: none">
+                                                       <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                               </span>
+                                               <com:TActiveLabel
+                                                       ID="BBconsJSONPathTestOk"
+                                                       Display="None"
+                                                       CssClass="w3-text-green"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                               </com:TActiveLabel>
+                                               <com:TActiveLabel
+                                                       ID="BBconsJSONPathTestErr"
+                                                       Display="None"
+                                                       CssClass="w3-text-red"
+                                                       EnableViewState="false"
+                                               >
+                                                       <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Connection error ]%>
+                                               </com:TActiveLabel>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="BconsCfgPath"
+                                                       Text="<%[ Admin Bconsole config file path (usually bconsole.conf): ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="BconsCfgPath"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TCustomValidator
+                                                       ValidationGroup="ConfigGroup"
+                                                       ControlToValidate="BconsCfgPath"
+                                                       ClientValidationFunction="bjsontools_validator"
+                                                       Display="Dynamic"
+                                                       Text="<%[ There is required to provide both binary file and config file paths. ]%>"
+                                               />
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <div class="w3-center w3-margin-top">
+                               <com:TActiveLinkButton
+                                       ID="TestJSONToolsConfig"
+                                       ValidationGroup="ConfigGroup"
+                                       OnClick="testJSONToolsCfg"
+                                       CssClass="w3-button w3-green"
+                               >
+                                       <prop:ClientSide.OnLoading>
+                                               bjsontools_hide_test_results();
+                                               $('.config_test_loader').show();
+                                       </prop:ClientSide.OnLoading>
+                                       <prop:ClientSide.OnComplete>
+                                               $('.config_test_loader').hide();
+                                       </prop:ClientSide.OnComplete>
+                                       <i class="fas fa-play"></i> &nbsp;<%[ Test configuration ]%>
+                               </com:TActiveLinkButton>
+                       </div>
+               </div>
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-center">
+                               <com:TActiveLinkButton
+                                       ID="ConfigSave"
+                                       CssClass="w3-button w3-green"
+                                       OnCallback="saveConfig"
+                                       ValidationGroup="ConfigGroup"
+                                       CausesValidation="true"
+                               >
+                                       <i class="fas fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TActiveLinkButton>
+                       </div>
+               </div>
+       </div>
+       <div class="w3-container tab_item" id="settings_actions" style="display: none">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="ActionsEnabled" Text="<%[ Enabled: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
+                               <com:TActiveCheckBox
+                                       ID="ActionsEnabled"
+                                       CssClass="w3-check"
+                                       Attributes.onclick="$('#actions_container').slideToggle('fast')"
+                               />
+                       </div>
+               </div>
+               <div id="actions_container" style="display: <%=$this->ActionsEnabled->Checked ? 'block' : 'none'%>">
+                       <fieldset>
+                               <legend><%[ General configuration ]%></legend>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="ActionsUseSudo"
+                                                       Text="<%[ Use sudo: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TCheckBox
+                                                       ID="ActionsUseSudo"
+                                                       CssClass="w3-check"
+                                               /> &nbsp;<a href="javascript:void(0)" onclick="get_sudo_config('actions');"><%[ Get sudo configuration ]%></a>
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend>Director</legend>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="DirStartAction"
+                                                       Text="<%[ Director start command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="DirStartAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestDirStartActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       Text=""
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="dir_start"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestDirStartActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_dir_start .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_dir_start .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-play"></i> &nbsp;<%[ Start ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_dir_start">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="DirStopAction"
+                                                       Text="<%[ Director stop command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="DirStopAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestDirStopActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="dir_stop"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestDirStopActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_dir_stop .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_dir_stop .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-stop"></i> &nbsp;<%[ Stop ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_dir_stop">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="DirRestartAction"
+                                                       Text="<%[ Director restart command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="DirRestartAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestDirRestartActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="dir_restart"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestDirRestartActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_dir_restart .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_dir_restart .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-sync"></i> &nbsp;<%[ Restart ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_dir_restart">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend>Storage Daemon</legend>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="SdStartAction"
+                                                       Text="<%[ Storage Daemon start command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="SdStartAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestSdStartActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="sd_start"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestSdStartActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_sd_start .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_sd_start .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-play"></i> &nbsp;<%[ Start ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_sd_start">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="SdStopAction"
+                                                       Text="<%[ Storage Daemon stop command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="SdStopAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestSdStopActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="sd_stop"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestSdStopActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_sd_stop .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_sd_stop .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-stop"></i> &nbsp;<%[ Stop ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_sd_stop">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="SdRestartAction"
+                                                       Text="<%[ Storage Daemon restart command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="SdRestartAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestSdRestartActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="sd_restart"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestSdRestartActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_sd_restart .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_sd_restart .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-sync"></i> &nbsp;<%[ Restart ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_sd_restart">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend>File Daemon/Client</legend>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="FdStartAction"
+                                                       Text="<%[ File Daemon/Client start command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="FdStartAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestFdStartActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="fd_start"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestFdStartActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_fd_start .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_fd_start .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-play"></i> &nbsp;<%[ Start ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_fd_start">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="FdStopAction"
+                                                       Text="<%[ File Daemon/Client stop command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="FdStopAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestFdStopActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="fd_stop"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestFdStopActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_fd_stop .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_fd_stop .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-stop"></i> &nbsp;<%[ Stop ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_fd_stop">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-quarter">
+                                               <com:TLabel
+                                                       ForControl="FdRestartAction"
+                                                       Text="<%[ File Daemon/Client restart command: ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-col w3-threequarter">
+                                               <com:TTextBox
+                                                       ID="FdRestartAction"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       Width="370px"
+                                               />
+                                               <com:TActiveLinkButton
+                                                       ID="TestFdRestartActionCommand"
+                                                       ValidationGroup="ActionsGroup"
+                                                       OnCommand="testExecActionCommand"
+                                                       CommandParameter="fd_restart"
+                                                       CssClass="w3-button w3-green"
+                                                       Width="105px"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               actions_hide_test_results('<%=$this->TestFdRestartActionCommand->CommandParameter%>');
+                                                               $('#actions_test_result_fd_restart .action_test_loader').show();
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               $('#actions_test_result_fd_restart .action_test_loader').hide();
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-sync"></i> &nbsp;<%[ Restart ]%>
+                                               </com:TActiveLinkButton>
+                                               <span id="actions_test_result_fd_restart">
+                                                       <span class="action_test_loader" style="display: none">
+                                                               <i class="fas fa-sync fa-spin" title="<%[ Loading... ]%>"></i>
+                                                       </span>
+                                                       <span class="action_success w3-text-green" style="display: none">
+                                                               <i class="fas fa-check" title="<%[ OK ]%>"></i> &nbsp;<%[ OK ]%>
+                                                       </span>
+                                                       <span class="action_error w3-text-red" style="display: none">
+                                                               <i class="fas fa-exclamation-circle" title="<%[ Error ]%>"></i> &nbsp;<%[ Error ]%>
+                                                       </span>
+                                                       <span class="action_result w3-text-red"></span>
+                                               </span>
+                                       </div>
+                               </div>
+                       </fieldset>
+               </div>
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-center">
+                               <com:TActiveLinkButton
+                                       ID="ActionsSave"
+                                       CssClass="w3-button w3-green"
+                                       OnCallback="saveActions"
+                                       ValidationGroup="ActionsGroup"
+                                       CausesValidation="true"
+                               >
+                                       <i class="fas fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TActiveLinkButton>
+                       </div>
+               </div>
+       </div>
+       <div class="w3-container tab_item" id="settings_authentication" style="display: none">
+               <div class="w3-container">
+                       <com:TActiveRadioButton
+                               ID="AuthOAuth2"
+                               GroupName="SelectAuth"
+                               CssClass="w3-radio"
+                       />
+                       <com:TLabel
+                               ForControl="AuthOAuth2"
+                               Text="<%[ Use OAuth2 for authorization and authentication ]%>"
+                       />
+               </div>
+               <div class="w3-container">
+                       <com:TActiveRadioButton
+                               ID="AuthBasic"
+                               GroupName="SelectAuth"
+                               CssClass="w3-radio"
+                       />
+                       <com:TLabel
+                               ForControl="AuthBasic"
+                               Text="<%[ Use HTTP Basic authentication ]%>"
+                       />
+               </div>
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-center">
+                               <com:TActiveLinkButton
+                                       ID="AuthSave"
+                                       CssClass="w3-button w3-green"
+                                       OnCallback="saveAuth"
+                               >
+                                       <i class="fas fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TActiveLinkButton>
+                       </div>
+               </div>
+       </div>
+<com:TJuiDialog
+       ID="SudoConfigPopup"
+       Options.title="<%[ Sudo configuration ]%>"
+       Options.autoOpen="false",
+       Options.minWidth="820"
+       Options.minHeight="200"
+>
+       <p><%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%></p>
+       <p><strong><%[ Note ]%></strong> <%[ Please use visudo to add this configuration, otherwise please do remember to add empty line at the end of file. ]%>
+       <p><%[ Example sudo configuration for Apache web server user (RHEL, CentOS and others): ]%></p>
+       <pre id="sudo_config_apache"></pre>
+       <p><%[ Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others): ]%></p>
+       <pre id="sudo_config_lighttpd"></pre>
+       <p><%[ Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others): ]%></p>
+       <pre id="sudo_config_www_data"></pre>
+</com:TJuiDialog>
+<script>
+function get_sudo_config(type) {
+       var bin_fields = {
+               bconsole: [
+                       '<%=$this->BconsolePath->ClientID%>'
+               ],
+               config: [
+                       '<%=$this->BDirJSONPath->ClientID%>',
+                       '<%=$this->BSdJSONPath->ClientID%>',
+                       '<%=$this->BFdJSONPath->ClientID%>',
+                       '<%=$this->BBconsJSONPath->ClientID%>',
+               ],
+               actions: [
+                       '<%=$this->DirStartAction->ClientID%>',
+                       '<%=$this->DirStopAction->ClientID%>',
+                       '<%=$this->DirRestartAction->ClientID%>',
+                       '<%=$this->SdStartAction->ClientID%>',
+                       '<%=$this->SdStopAction->ClientID%>',
+                       '<%=$this->SdRestartAction->ClientID%>',
+                       '<%=$this->FdStartAction->ClientID%>',
+                       '<%=$this->FdStopAction->ClientID%>',
+                       '<%=$this->FdRestartAction->ClientID%>'
+               ]
+       }
+       var val, pre;
+       var cfg = '';
+       var users = ['apache', 'lighttpd', 'www-data'];
+       var fields = bin_fields.hasOwnProperty(type) ? bin_fields[type] : [];
+       for (var i = 0; i < users.length; i++) {
+               var pre = document.getElementById('sudo_config_' + users[i].replace(/-/g, '_'));
+               pre.textContent = 'Defaults:' + users[i] + ' !requiretty' + "\n";
+               for (var j = 0; j < fields.length; j++) {
+                       val = document.getElementById(fields[j]).value.trim();
+                       if (val) {
+                               pre.textContent += users[i] + ' ALL = (root) NOPASSWD: ' + val + "\n";
+                       }
+               }
+       }
+       $('#<%=$this->SudoConfigPopup->ClientID%>').dialog('open');
+}
+
+var bjsontools_fields = {
+       General: {
+               data: [],
+               test: [$('#<%=$this->BConfigDirTestOk->ClientID%>'), $('#<%=$this->BConfigDirTestErr->ClientID%>')]
+       },
+       Dir: {
+               data: [$('#<%=$this->BDirJSONPath->ClientID%>'), $('#<%=$this->DirCfgPath->ClientID%>')],
+               test: [$('#<%=$this->BDirJSONPathTestOk->ClientID%>'), $('#<%=$this->BDirJSONPathTestErr->ClientID%>')]
+       },
+       Sd: {
+               data: [$('#<%=$this->BSdJSONPath->ClientID%>'), $('#<%=$this->SdCfgPath->ClientID%>')],
+               test: [$('#<%=$this->BSdJSONPathTestOk->ClientID%>'), $('#<%=$this->BSdJSONPathTestErr->ClientID%>')]
+       },
+       Fd: {
+               data: [$('#<%=$this->BFdJSONPath->ClientID%>'), $('#<%=$this->FdCfgPath->ClientID%>')],
+               test: [$('#<%=$this->BFdJSONPathTestOk->ClientID%>'), $('#<%=$this->BFdJSONPathTestErr->ClientID%>')]
+       },
+       Bcons: {
+               data: [$('#<%=$this->BBconsJSONPath->ClientID%>'), $('#<%=$this->BconsCfgPath->ClientID%>')],
+               test: [$('#<%=$this->BBconsJSONPathTestOk->ClientID%>'), $('#<%=$this->BBconsJSONPathTestErr->ClientID%>')]
+       }
+};
+var bjsontools_get_field_type = function(field_id) {
+       var field_types = Object.keys(bjsontools_fields);
+       var type, field_type;
+       for (var i = 0; i < field_types.length; i++) {
+               type = field_types[i];
+               for (var j = 0; j < bjsontools_fields[type].data.length; j++) {
+                       if (bjsontools_fields[type].data[j][0].id === field_id) {
+                               field_type = type;
+                               break;
+                       }
+               }
+               if (field_type) {
+                       break;
+               }
+       }
+       return field_type;
+};
+var bjsontools_validator = function(sender, parameter) {
+       var validation_css = 'validation-error';
+       var id_validating_field = sender.control.id;
+       var type = bjsontools_get_field_type(id_validating_field);
+       var invalid_fields = [];
+       for (var i = 0; i < bjsontools_fields[type].data.length; i++) {
+               bjsontools_fields[type].data[i].removeClass(validation_css);
+               if (!bjsontools_fields[type].data[i][0].value) {
+                       invalid_fields.push(bjsontools_fields[type].data[i]);
+               }
+       }
+       var valid = (invalid_fields.length === 0 || invalid_fields.length === bjsontools_fields[type].data.length);
+       var is_validating_field_valid = true;
+       if (!valid) {
+               for (var i = 0; i < invalid_fields.length; i++) {
+                       invalid_fields[i].addClass(validation_css);
+                       if (invalid_fields[i][0].id === id_validating_field) {
+                               is_validating_field_valid = false;
+                       }
+               }
+               bjsontools_hide_test_results(type);
+       }
+       if (is_validating_field_valid === true) {
+               valid = true;
+       }
+
+       return valid;
+};
+
+// if field_type not given then all test results become hidden
+var bjsontools_hide_test_results = function(field_type) {
+       var field_types = Object.keys(bjsontools_fields);
+       var type;
+       for (var i = 0; i < field_types.length; i++) {
+               type = field_types[i];
+               for (var j = 0; j < bjsontools_fields[type].test.length; j++) {
+                       if (!field_type || field_type === type) {
+                               bjsontools_fields[type].test[j].hide();
+                       }
+               }
+       }
+};
+var set_action_command_output = function(action, result) {
+       var result_el = document.querySelector('#actions_test_result_' + action + ' .action_result');
+       if (result.error === 0) {
+               var success = document.querySelector('#actions_test_result_' + action + ' .action_success');
+               success.style.display = '';
+               result_el.textContent = '';
+       } else {
+               var error = document.querySelector('#actions_test_result_' + action + ' .action_error');
+               error.style.display = '';
+               result_el.innerHTML = '<br />' + result.output.join('<br />');
+       }
+};
+
+var actions_hide_test_results = function(action) {
+       var loader = document.querySelector('#actions_test_result_' + action + ' .action_test_loader');
+       var success = document.querySelector('#actions_test_result_' + action + ' .action_success');
+       var error = document.querySelector('#actions_test_result_' + action + ' .action_error');
+       var result = document.querySelector('#actions_test_result_' + action + ' .action_result');
+       loader.style.display = 'none';
+       success.style.display = 'none'
+       error.style.display = 'none';
+       result.textContent = '';
+};
+
+function reload_page_cb() {
+       window.location.reload();
+}
+</script>
+</com:TContent>
diff --git a/gui/baculum/protected/API/Pages/Panel/APISettings.php b/gui/baculum/protected/API/Pages/Panel/APISettings.php
new file mode 100644 (file)
index 0000000..5516ffd
--- /dev/null
@@ -0,0 +1,475 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.Common.Class.AuthBasic');
+Prado::using('Application.Common.Class.AuthOAuth2');
+Prado::using('Application.API.Class.BaculumAPIPage');
+Prado::using('Application.API.Class.BAPIException');
+
+/**
+ * API settings page.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Panel
+ * @package Baculum API
+ */
+class APISettings extends BaculumAPIPage {
+
+       public $config;
+
+       const DEFAULT_ACTION_DIR_START = '/usr/bin/systemctl start bacula-dir';
+       const DEFAULT_ACTION_DIR_STOP = '/usr/bin/systemctl stop bacula-dir';
+       const DEFAULT_ACTION_DIR_RESTART = '/usr/bin/systemctl restart bacula-dir';
+       const DEFAULT_ACTION_SD_START = '/usr/bin/systemctl start bacula-sd';
+       const DEFAULT_ACTION_SD_STOP = '/usr/bin/systemctl stop bacula-sd';
+       const DEFAULT_ACTION_SD_RESTART = '/usr/bin/systemctl restart bacula-sd';
+       const DEFAULT_ACTION_FD_START = '/usr/bin/systemctl start bacula-fd';
+       const DEFAULT_ACTION_FD_STOP = '/usr/bin/systemctl stop bacula-fd';
+       const DEFAULT_ACTION_FD_RESTART = '/usr/bin/systemctl restart bacula-fd';
+
+       public function onInit($param) {
+               parent::onInit($param);
+               $config = $this->getModule('api_config');
+               $this->config = $config->getConfig();
+               $this->loadGeneralSettings();
+               $this->loadDbSettings();
+               $this->loadBconsoleSettings();
+               $this->loadConfigSettings();
+               $this->loadActionsSettings();
+               $this->loadAuthSettings();
+       }
+
+       private function loadGeneralSettings() {
+               if ($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
+               $this->GeneralLang->SelectedValue= $this->config['api']['lang'];
+               $this->GeneralDebug->Checked = ($this->config['api']['debug'] == 1);
+       }
+
+       private function loadDbSettings() {
+               if ($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
+               $this->DBEnabled->Checked = ($this->config['db']['enabled'] == 1);
+               $this->DBType->SelectedValue = $this->config['db']['type'];
+               $this->DBName->Text = $this->config['db']['name'];
+               $this->DBLogin->Text = $this->config['db']['login'];
+               $this->DBPassword->Text = $this->config['db']['password'];
+               $this->DBAddress->Text = $this->config['db']['ip_addr'];
+               $this->DBPort->Text = $this->config['db']['port'];
+               $this->DBPath->Text = $this->config['db']['path'];
+               $this->setDBType(null, null);
+       }
+
+       private function loadBconsoleSettings() {
+               if ($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
+               $this->BconsoleEnabled->Checked = ($this->config['bconsole']['enabled'] == 1);
+               $this->BconsolePath->Text = $this->config['bconsole']['bin_path'];
+               $this->BconsoleConfigPath->Text = $this->config['bconsole']['cfg_path'];
+               $this->BconsoleUseSudo->Checked = ($this->config['bconsole']['use_sudo'] == 1);
+       }
+
+       private function loadConfigSettings() {
+               if ($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
+               $this->ConfigEnabled->Checked = ($this->config['jsontools']['enabled'] == 1);
+               if (key_exists('use_sudo', $this->config['jsontools'])) {
+                       $this->BJSONUseSudo->Checked = $this->config['jsontools']['use_sudo'];
+               }
+               if (key_exists('bconfig_dir', $this->config['jsontools'])) {
+                       $this->BConfigDir->Text = $this->config['jsontools']['bconfig_dir'];
+               }
+               if (key_exists('bdirjson_path', $this->config['jsontools'])) {
+                       $this->BDirJSONPath->Text = $this->config['jsontools']['bdirjson_path'];
+               }
+               if (key_exists('dir_cfg_path', $this->config['jsontools'])) {
+                       $this->DirCfgPath->Text = $this->config['jsontools']['dir_cfg_path'];
+               }
+               if (key_exists('bsdjson_path', $this->config['jsontools'])) {
+                       $this->BSdJSONPath->Text = $this->config['jsontools']['bsdjson_path'];
+               }
+               if (key_exists('sd_cfg_path', $this->config['jsontools'])) {
+                       $this->SdCfgPath->Text = $this->config['jsontools']['sd_cfg_path'];
+               }
+               if (key_exists('bfdjson_path', $this->config['jsontools'])) {
+                       $this->BFdJSONPath->Text = $this->config['jsontools']['bfdjson_path'];
+               }
+               if (key_exists('fd_cfg_path', $this->config['jsontools'])) {
+                       $this->FdCfgPath->Text = $this->config['jsontools']['fd_cfg_path'];
+               }
+               if (key_exists('bbconsjson_path', $this->config['jsontools'])) {
+                       $this->BBconsJSONPath->Text = $this->config['jsontools']['bbconsjson_path'];
+               }
+               if (key_exists('bcons_cfg_path', $this->config['jsontools'])) {
+                       $this->BconsCfgPath->Text = $this->config['jsontools']['bcons_cfg_path'];
+               }
+       }
+
+       private function loadActionsSettings() {
+               if ($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
+               if (!key_exists('actions', $this->config)) {
+                       $this->DirStartAction->Text = self::DEFAULT_ACTION_DIR_START;
+                       $this->DirStopAction->Text = self::DEFAULT_ACTION_DIR_STOP;
+                       $this->DirRestartAction->Text = self::DEFAULT_ACTION_DIR_RESTART;
+                       $this->SdStartAction->Text = self::DEFAULT_ACTION_SD_START;
+                       $this->SdStopAction->Text = self::DEFAULT_ACTION_SD_STOP;
+                       $this->SdRestartAction->Text = self::DEFAULT_ACTION_SD_RESTART;
+                       $this->FdStartAction->Text = self::DEFAULT_ACTION_FD_START;
+                       $this->FdStopAction->Text = self::DEFAULT_ACTION_FD_STOP;
+                       $this->FdRestartAction->Text = self::DEFAULT_ACTION_FD_RESTART;
+                       return;
+               }
+
+               if (key_exists('enabled', $this->config['actions'])) {
+                       $this->ActionsEnabled->Checked = ($this->config['actions']['enabled'] == 1);
+               }
+               if (key_exists('use_sudo', $this->config['actions'])) {
+                       $this->ActionsUseSudo->Checked = $this->config['actions']['use_sudo'];
+               }
+               if (key_exists('dir_start', $this->config['actions'])) {
+                       $this->DirStartAction->Text = $this->config['actions']['dir_start'];
+               }
+               if (key_exists('dir_stop', $this->config['actions'])) {
+                       $this->DirStopAction->Text = $this->config['actions']['dir_stop'];
+               }
+               if (key_exists('dir_restart', $this->config['actions'])) {
+                       $this->DirRestartAction->Text = $this->config['actions']['dir_restart'];
+               }
+               if (key_exists('sd_start', $this->config['actions'])) {
+                       $this->SdStartAction->Text = $this->config['actions']['sd_start'];
+               }
+               if (key_exists('sd_stop', $this->config['actions'])) {
+                       $this->SdStopAction->Text = $this->config['actions']['sd_stop'];
+               }
+               if (key_exists('sd_restart', $this->config['actions'])) {
+                       $this->SdRestartAction->Text = $this->config['actions']['sd_restart'];
+               }
+               if (key_exists('fd_start', $this->config['actions'])) {
+                       $this->FdStartAction->Text = $this->config['actions']['fd_start'];
+               }
+               if (key_exists('fd_stop', $this->config['actions'])) {
+                       $this->FdStopAction->Text = $this->config['actions']['fd_stop'];
+               }
+               if (key_exists('fd_restart', $this->config['actions'])) {
+                       $this->FdRestartAction->Text = $this->config['actions']['fd_restart'];
+               }
+       }
+
+       private function loadAuthSettings() {
+               if ($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
+               if ($this->config['api']['auth_type'] === AuthBasic::NAME) {
+                       $this->AuthBasic->Checked = true;
+               } elseif ($this->config['api']['auth_type'] === AuthOAuth2::NAME) {
+                       $this->AuthOAuth2->Checked = true;
+               }
+       }
+
+       public function setDBType($sender, $param) {
+               $db = $this->DBType->SelectedValue;
+               $this->setDBLogin($db);
+               $this->setDBPassword($db);
+               $this->setDBAddress($db);
+               $this->setDBPort($db);
+               $this->setDBPath($db);
+       }
+
+       public function setDBLogin($db) {
+               $this->DBLogin->Enabled = ($db !== Database::SQLITE_TYPE);
+       }
+
+       public function setDBPassword($db) {
+               $this->DBPassword->Enabled = ($db !== Database::SQLITE_TYPE);
+
+       }
+
+       public function setDBAddress($db) {
+               $this->DBAddress->Enabled = ($db !== Database::SQLITE_TYPE);
+       }
+
+       public function setDBPort($db) {
+               $port = null;
+               if(Database::PGSQL_TYPE === $db) {
+                       $port = 5432;
+               } elseif(Database::MYSQL_TYPE === $db) {
+                       $port = 3306;
+               } elseif(Database::SQLITE_TYPE === $db) {
+                       $port = null;
+               }
+
+               $prevPort = $this->DBPort->getViewState('port');
+
+               if(is_null($port)) {
+                       $this->DBPort->Text = '';
+                       $this->DBPort->Enabled = false;
+               } else {
+                       $this->DBPort->Enabled = true;
+                       $this->DBPort->Text = (empty($prevPort)) ? $port : $prevPort;
+               }
+               $this->DBPort->setViewState('port', '');
+       }
+
+       public function setDBPath($db) {
+               if ($db === Database::SQLITE_TYPE) {
+                       $this->DBPath->Enabled = true;
+                       $this->DBPathField->Display = 'Fixed';
+               } else {
+                       $this->DBPath->Enabled = false;
+                       $this->DBPathField->Display = 'Hidden';
+               }
+       }
+
+       public function connectionDBTest($sender, $param) {
+               $validation = false;
+               $db_params = array();
+               $db_params['type'] = $this->DBType->SelectedValue;
+               if($db_params['type'] === Database::MYSQL_TYPE || $db_params['type'] === Database::PGSQL_TYPE) {
+                       $db_params['name'] = $this->DBName->Text;
+                       $db_params['login'] = $this->DBLogin->Text;
+                       $db_params['password'] = $this->DBPassword->Text;
+                       $db_params['ip_addr'] = $this->DBAddress->Text;
+                       $db_params['port'] = $this->DBPort->Text;
+                       $validation = true;
+               } elseif($db_params['type'] === Database::SQLITE_TYPE && !empty($this->DBPath->Text)) {
+                       $db_params['path'] = $this->DBPath->Text;
+                       $validation = true;
+               }
+
+               $is_validate = false;
+               $emsg = '';
+               if ($validation === true) {
+                       try {
+                               $is_validate = $this->getModule('db')->testDbConnection($db_params);
+                       } catch (BAPIException $e) {
+                               $emsg = $e->getErrorMessage();
+                       }
+               }
+               if (!empty($emsg)) {
+                       $this->DbTestResultErr->Text = $emsg;
+               }
+               if ($is_validate === true) {
+                       $this->getCallbackClient()->show('db_test_result_ok');
+                       $this->getCallbackClient()->hide('db_test_result_err');
+                       $this->getCallbackClient()->hide($this->DbTestResultErr);
+               } else {
+                       $this->getCallbackClient()->hide('db_test_result_ok');
+                       $this->getCallbackClient()->show('db_test_result_err');
+                       $this->getCallbackClient()->show($this->DbTestResultErr);
+               }
+       }
+
+       public function connectionBconsoleTest($sender, $param) {
+               $emsg = '';
+               $result = $this->getModule('bconsole')->testBconsoleCommand(
+                       array('version'),
+                       $this->BconsolePath->Text,
+                       $this->BconsoleConfigPath->Text,
+                       $this->BconsoleUseSudo->Checked
+               );
+               $is_validate = ($result->exitcode === 0);
+               if (!$is_validate) {
+                       $this->BconsoleTestResultErr->Text = $result->output;
+               }
+               if ($is_validate === true) {
+                       $this->getCallbackClient()->show('bconsole_test_result_ok');
+                       $this->getCallbackClient()->hide('bconsole_test_result_err');
+                       $this->getCallbackClient()->hide($this->BconsoleTestResultErr);
+               } else {
+                       $this->getCallbackClient()->hide('bconsole_test_result_ok');
+                       $this->getCallbackClient()->show('bconsole_test_result_err');
+                       $this->getCallbackClient()->show($this->BconsoleTestResultErr);
+               }
+       }
+
+       public function testJSONToolsCfg($sender, $param) {
+               $jsontools = array(
+                       'dir' => array(
+                               'path' => $this->BDirJSONPath->Text,
+                               'cfg' => $this->DirCfgPath->Text,
+                               'ok_el' => $this->BDirJSONPathTestOk,
+                               'error_el' => $this->BDirJSONPathTestErr
+                       ),
+                       'sd' => array(
+                               'path' => $this->BSdJSONPath->Text,
+                               'cfg' => $this->SdCfgPath->Text,
+                               'ok_el' => $this->BSdJSONPathTestOk,
+                               'error_el' => $this->BSdJSONPathTestErr
+                       ),
+                       'fd' => array(
+                               'path' => $this->BFdJSONPath->Text,
+                               'cfg' => $this->FdCfgPath->Text,
+                               'ok_el' => $this->BFdJSONPathTestOk,
+                               'error_el' => $this->BFdJSONPathTestErr
+                       ),
+                       'bcons' => array(
+                               'path' => $this->BBconsJSONPath->Text,
+                               'cfg' => $this->BconsCfgPath->Text,
+                               'ok_el' => $this->BBconsJSONPathTestOk,
+                               'error_el' => $this->BBconsJSONPathTestErr
+                       )
+               );
+               $use_sudo = $this->BJSONUseSudo->Checked;
+
+               foreach ($jsontools as $type => $config) {
+                       $config['ok_el']->Display = 'None';
+                       $config['error_el']->Display = 'None';
+                       if (!empty($config['path']) && !empty($config['cfg'])) {
+
+                               $result = (object)$this->getModule('json_tools')->testJSONTool($config['path'], $config['cfg'], $use_sudo);
+                               if ($result->exitcode === 0) {
+                                       // test passed
+                                       $config['ok_el']->Display = 'Dynamic';
+                               } else {
+                                       // test failed
+                                       $config['error_el']->Text = implode("\n", $result->output);
+                                       $config['error_el']->Display = 'Dynamic';
+                               }
+                       }
+               }
+       }
+
+       public function testConfigDir($sender, $param) {
+               $valid = is_writeable($this->BConfigDir->Text);
+               $this->BConfigDirTestOk->Display = 'None';
+               $this->BConfigDirTestErr->Display = 'None';
+               $this->BConfigDirWritableTest->Display = 'None';
+               if ($valid === true) {
+                       $this->BConfigDirTestOk->Display = 'Dynamic';
+               } else {
+                       $this->BConfigDirWritableTest->Display = 'Dynamic';
+                       $this->BConfigDirTestErr->Display = 'Dynamic';
+               }
+               $param->setIsValid($valid);
+       }
+
+       public function testExecActionCommand($sender, $param) {
+               $action = $param->CommandParameter;
+               $cmd = '';
+               switch ($action) {
+                       case 'dir_start': $cmd = $this->DirStartAction->Text; break;
+                       case 'dir_stop': $cmd = $this->DirStopAction->Text; break;
+                       case 'dir_restart': $cmd = $this->DirRestartAction->Text; break;
+                       case 'sd_start': $cmd = $this->SdStartAction->Text; break;
+                       case 'sd_stop': $cmd = $this->SdStopAction->Text; break;
+                       case 'sd_restart': $cmd = $this->SdRestartAction->Text; break;
+                       case 'fd_start': $cmd = $this->FdStartAction->Text; break;
+                       case 'fd_stop': $cmd = $this->FdStopAction->Text; break;
+                       case 'fd_restart': $cmd = $this->FdRestartAction->Text; break;
+               };
+               $result = $this->getModule('comp_actions')->execCommand($cmd, $this->ActionsUseSudo->Checked);
+               $this->getCallbackClient()->callClientFunction('set_action_command_output', array($action, (array)$result));
+       }
+
+       public function saveGeneral($sender, $param) {
+               $reload_page = false;
+               if ($this->config['api']['lang'] != $this->GeneralLang->SelectedValue) {
+                       $reload_page = true;
+               }
+               $this->config['api']['lang'] = $this->GeneralLang->SelectedValue;
+               $this->config['api']['debug'] = $this->GeneralDebug->Checked ? 1 : 0;
+               $this->getModule('api_config')->setConfig($this->config);
+               if ($reload_page) {
+                       $this->getCallbackClient()->callClientFunction('reload_page_cb', []);
+               }
+       }
+
+       public function saveCatalog($sender, $param) {
+               $cfg = [
+                       'enabled' => ($this->DBEnabled->Checked ? 1 : 0),
+                       'type' => $this->DBType->SelectedValue,
+                       'name' => $this->DBName->Text,
+                       'login' => $this->DBLogin->Text,
+                       'password' => $this->DBPassword->Text,
+                       'ip_addr' => $this->DBAddress->Text,
+                       'port' => $this->DBPort->Text,
+                       'path' => ($this->DBType->SelectedValue === Database::SQLITE_TYPE) ? $this->DBPath->Text : ''
+               ];
+               $this->config['db'] = $cfg;
+               $this->getModule('api_config')->setConfig($this->config);
+       }
+
+       public function saveBconsole($sender, $param) {
+               $cfg = [
+                       'enabled' => ($this->BconsoleEnabled->Checked ? 1 : 0),
+                       'bin_path' => $this->BconsolePath->Text,
+                       'cfg_path' => $this->BconsoleConfigPath->Text,
+                       'use_sudo' => ($this->BconsoleUseSudo->Checked ? 1 : 0)
+               ];
+               $this->config['bconsole'] = $cfg;
+               $this->getModule('api_config')->setConfig($this->config);
+       }
+
+       public function saveConfig($sender, $param) {
+               $cfg = [
+                       'enabled' => ($this->ConfigEnabled->Checked ? 1 : 0),
+                       'use_sudo' => ($this->BJSONUseSudo->Checked ? 1 : 0),
+                       'bconfig_dir' => $this->BConfigDir->Text,
+                       'bdirjson_path' => $this->BDirJSONPath->Text,
+                       'dir_cfg_path' => $this->DirCfgPath->Text,
+                       'bsdjson_path' => $this->BSdJSONPath->Text,
+                       'sd_cfg_path' => $this->SdCfgPath->Text,
+                       'bfdjson_path' => $this->BFdJSONPath->Text,
+                       'fd_cfg_path' => $this->FdCfgPath->Text,
+                       'bbconsjson_path' => $this->BBconsJSONPath->Text,
+                       'bcons_cfg_path' => $this->BconsCfgPath->Text
+               ];
+               $this->config['jsontools'] = $cfg;
+               $this->getModule('api_config')->setConfig($this->config);
+       }
+
+       public function saveActions($sender, $param) {
+               $cfg = [
+                       'enabled' => ($this->ActionsEnabled->Checked ? 1 : 0),
+                       'use_sudo' => ($this->ActionsUseSudo->Checked ? 1 : 0),
+                       'dir_start' => $this->DirStartAction->Text,
+                       'dir_stop' => $this->DirStopAction->Text,
+                       'dir_restart' => $this->DirRestartAction->Text,
+                       'sd_start' => $this->SdStartAction->Text,
+                       'sd_stop' => $this->SdStopAction->Text,
+                       'sd_restart' => $this->SdRestartAction->Text,
+                       'fd_start' => $this->FdStartAction->Text,
+                       'fd_stop' => $this->FdStopAction->Text,
+                       'fd_restart' => $this->FdRestartAction->Text
+               ];
+               $this->config['actions'] = $cfg;
+               $this->getModule('api_config')->setConfig($this->config);
+       }
+
+       public function saveAuth($sender, $param) {
+               $auth_type = AuthBasic::NAME;
+               if ($this->AuthOAuth2->Checked) {
+                       $auth_type = AuthOAuth2::NAME;
+               } elseif ($this->AuthBasic->Checked) {
+                       $auth_type = AuthBasic::NAME;
+               }
+               $this->config['api']['auth_type'] = $auth_type;
+               $this->getModule('api_config')->setConfig($this->config);
+       }
+}
+?>
index d4b74bc332b2795e092c7daad752968231091a1c..81e24edbe6530d376ca9dbb475c7928eb6ae344a 100644 (file)
@@ -1,5 +1,8 @@
 <urls>
        <!-- Pages -->
-       <url ServiceParameter="APIInstallWizard" pattern="panel/config/" />
        <url ServiceParameter="APIHome" pattern="panel/" />
+       <url ServiceParameter="APIInstallWizard" pattern="panel/config/" />
+       <url ServiceParameter="APIBasicUsers" pattern="panel/basic/" />
+       <url ServiceParameter="APIOAuth2Clients" pattern="panel/oauth2/" />
+       <url ServiceParameter="APISettings" pattern="panel/settings/" />
 </urls>
diff --git a/gui/baculum/protected/API/Portlets/APISideBar.php b/gui/baculum/protected/API/Portlets/APISideBar.php
new file mode 100644 (file)
index 0000000..cafc420
--- /dev/null
@@ -0,0 +1,57 @@
+<?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.TActiveLinkButton');
+Prado::using('Application.Common.Portlets.PortletTemplate');
+
+/**
+ * Main side-bar control.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Control
+ * @package Baculum API
+ */
+class APISideBar extends PortletTemplate {
+       /**
+        * Reload URL is used to refresh page after logout with Basic auth.
+        */
+       public $reload_url = '';
+
+       public function onInit($param) {
+               parent::onInit($param);
+               $fake_pwd = $this->getModule('crypto')->getRandomString();
+               // must be different than currently logged in Basic user
+               $user = (isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '') . '1';
+
+               // do a login try with different user and password to logout current user
+               $this->reload_url = $this->getPage()->getFullLoginUrl($user, $fake_pwd);
+       }
+
+       public function logout($sender, $param) {
+               /**
+                * This status code 401 is necessary to stop comming AJAX requests
+                * and to bring the login prompt on.
+                */
+               $this->Response->setStatusCode(401);
+       }
+}
+?>
diff --git a/gui/baculum/protected/API/Portlets/APISideBar.tpl b/gui/baculum/protected/API/Portlets/APISideBar.tpl
new file mode 100644 (file)
index 0000000..ff29343
--- /dev/null
@@ -0,0 +1,41 @@
+<!-- Sidebar/menu -->
+<nav class="w3-sidebar w3-white w3-animate-left w3-margin-bottom" style="z-index:3; width:250px;" id="sidebar">
+       <div class="w3-container w3-row w3-section">
+               <div class="w3-col s3">
+                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/avatar2.png" class="w3-circle w3-margin-right" style="width: 33px" />
+               </div>
+               <div class="w3-col s9 w3-bar">
+                       <span><%[ Welcome ]%><strong><%=isset($_SERVER['PHP_AUTH_USER']) ? ', ' . $_SERVER['PHP_AUTH_USER'] : ''%></strong></span><br>
+                       <script>var main_side_bar_reload_url = '<%=$this->reload_url%>';</script>
+                       <com:TActiveLinkButton
+                               ID="Logout"
+                               OnClick="logout"
+                               CssClass="w3-bar-item w3-button"
+                               ToolTip="<%[ Logout ]%>"
+                       >
+                               <prop:ClientSide.OnComplete>
+                                       if (!window.chrome || window.navigator.webdriver)  {
+                                               window.location.href = main_side_bar_reload_url;
+                                       } else if (window.chrome) {
+                                               // For chrome this reload is required to show login Basic auth prompt
+                                               window.location.reload();
+                                       }
+                               </prop:ClientSide.OnComplete>
+                               <i class="fa fa-power-off"></i>
+                       </com:TActiveLinkButton>
+               </div>
+       </div>
+       <div class="w3-container w3-black">
+               <h5>Baculum API Menu</h5>
+       </div>
+       <div class="w3-bar-block" style="margin-bottom: 45px;">
+               <div class="w3-black" style="height: 3px"></div>
+               <a href="<%=$this->Service->constructUrl('APIHome')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIHome' ? ' w3-blue': ''%>"><i class="fas fa-tachometer-alt fa-fw"></i> &nbsp;<%[ Dashboard ]%></a>
+               <a href="<%=$this->Service->constructUrl('APIBasicUsers')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIBasicUsers' ? ' w3-blue': ''%>"><i class="fa fa-users fa-fw"></i> &nbsp;<%[ Basic users ]%></a>
+               <a href="<%=$this->Service->constructUrl('APIOAuth2Clients')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIOAuth2Clients' ? ' w3-blue': ''%>"><i class="fa fa-user-shield fa-fw"></i> &nbsp;<%[ OAuth2 clients ]%></a>
+               <a href="<%=$this->Service->constructUrl('APISettings')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APISettings' ? ' w3-blue': ''%>"><i class="fa fa-wrench fa-fw"></i> &nbsp;<%[ Settings ]%></a>
+               <a href="<%=$this->Service->constructUrl('APIInstallWizard')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'APIInstallWizard' ? ' w3-blue': ''%>"><i class="fa fa-hat-wizard fa-fw"></i> &nbsp;<%[ Configuration wizard ]%></a>
+       </div>
+</nav>
+<!-- Overlay effect when opening sidebar on small screens -->
+<div class="w3-overlay w3-hide-large w3-animate-opacity" onclick="W3SideBar.close(); return false;" style="cursor:pointer" title="close side menu" id="overlay_bg"></div>
index 1c981320e72a393676536c686c3e1eb0c57da093..af963dca8404b7060ca332b919cbb58d5f448673 100644 (file)
@@ -12,7 +12,7 @@
                        "name": "AGPLv3",
                        "url": "https://www.gnu.org/licenses/agpl-3.0-standalone.html"
                },
-               "version": "1.0.0"
+               "version": "2.0.0"
        },
        "server": {
                "servers": [{
                "description": "Bacula Client endpoints",
                "externalDocs": {
                        "description": "Find out more",
-                       "url": "https://www.bacula.org/9.6.x-manuals/en/main/Configuring_Director.html#SECTION0021130000000000000000"
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/Client_File_daemon_Configur.html#SECTION0022100000000000000000"
                }
        },
        {
                "description": "Bacula Job endpoints",
                "externalDocs": {
                        "description": "Find out more",
-                       "url": "https://www.bacula.org/9.6.x-manuals/en/main/Configuring_Director.html#SECTION002130000000000000000"
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/Configuring_Director.html#SECTION0021300000000000000000"
                }
        },
        {
                "description": "Bacula Storage endpoints",
                "externalDocs": {
                        "description": "Find out more",
-                       "url": "https://www.bacula.org/9.6.x-manuals/en/main/Configuring_Director.html#SECTION0021140000000000000000"
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/Configuring_Director.html#SECTION00211400000000000000000"
                }
        },
        {
                "description": "Bacula Pool endpoints",
                "externalDocs": {
                        "description": "Find out more",
-                       "url": "https://www.bacula.org/9.6.x-manuals/en/main/Configuring_Director.html#SECTION0021160000000000000000"
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/Configuring_Director.html#SECTION00211600000000000000000"
                }
        },
        {
                "description": "Bacula Volume endpoints",
                "externalDocs": {
                        "description": "Find out more",
-                       "url": "https://www.bacula.org/9.6.x-manuals/en/main/Basic_Volume_Management.html"
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/Basic_Volume_Management.html"
                }
        },
        {
                "description": "Bacula FileSet endpoints",
                "externalDocs": {
                        "description": "Find out more",
-                       "url": "https://www.bacula.org/9.6.x-manuals/en/main/Configuring_Director.html#SECTION002170000000000000000"
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/Configuring_Director.html#SECTION0021700000000000000000"
                }
        },
        {
                "description": "Bacula Schedule endpoints",
                "externalDocs": {
                        "description": "Find out more",
-                       "url": "https://www.bacula.org/9.6.x-manuals/en/main/Configuring_Director.html#SECTION002150000000000000000"
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/Configuring_Director.html#SECTION0021500000000000000000"
                }
        },
        {
                "description": "Bacula BVFS endpoints",
                "externalDocs": {
                        "description": "Find out more",
-                       "url": "https://www.bacula.org/9.6.x-manuals/en/main/New_Features_in_5_2_13.html#SECTION001014000000000000000"
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/New_Features_in_5_2_13.html#SECTION0010140000000000000000"
                }
        },
        {
                "description": "Bacula joblog endpoints"
        },
        {
-               "name": "status",
-               "description": "Bacula status endpoints"
-       }],
+               "name": "directors",
+               "description": "Bacula director endpoints",
+               "externalDocs": {
+                       "description": "Find out more",
+                       "url": "https://www.bacula.org/11.0.x-manuals/en/main/Configuring_Director.html"
+               }
+       }
+       ],
        "paths": {
-               "/api/v1/clients": {
+               "/api/v2/clients": {
                        "get": {
                                "tags": ["clients"],
                                "summary": "Client list",
                                }]
                        }
                },
-               "/api/v1/clients/{clientid}": {
+               "/api/v2/clients/{clientid}": {
                        "get": {
                                "tags": ["clients"],
                                "summary": "Find client by ClientId",
                                }]
                        }
                },
-               "/api/v1/clients/{clientid}/show": {
+               "/api/v2/clients/{clientid}/show": {
                        "get": {
                                "tags": ["clients"],
                                "summary": "Show client",
                                ]
                        }
                },
-               "/api/v1/clients/{clientid}/status": {
+               "/api/v2/clients/{clientid}/status": {
                        "get": {
                                "tags": ["clients"],
                                "summary": "Client status",
                                                                                        "type": "array",
                                                                                        "items": {
                                                                                                "type": "string",
-                                                                                               "description": "Client status"
+                                                                                               "description": "Client status output"
                                                                                        }
                                                                                },
                                                                                "error": {
                                                }
                                        }
                                },
-                               "parameters": [{
-                                       "$ref": "#/components/parameters/ClientId"
-                               }]
+                               "parameters": [
+                                       {
+                                               "$ref": "#/components/parameters/ClientId"
+                                       },
+                                       {
+                                               "$ref": "#/components/parameters/Output"
+                                       },
+                                       {
+                                               "name": "type",
+                                               "in": "query",
+                                               "description": "Output type using together with output=json parameter.",
+                                               "schema": {
+                                                       "type": "string",
+                                                       "enum": ["header", "running", "terminated"]
+                                               }
+                                       }
+                               ]
                        }
                },
-               "/api/v1/clients/{clientid}/jobs": {
+               "/api/v2/clients/{clientid}/jobs": {
                        "get": {
                                "tags": ["clients"],
                                "summary": "Jobs for client",
                                }]
                        }
                },
-               "/api/v1/clients/{clientid}/ls": {
+               "/api/v2/clients/{clientid}/ls": {
                        "get": {
                                "tags": ["clients"],
                                "summary": "List Client files/directories",
                                ]
                        }
                },
-               "/api/v1/clients/{clientid}/bandwidth": {
+               "/api/v2/clients/{clientid}/bandwidth": {
                        "put": {
                                "tags": ["clients"],
                                "summary": "Set Client bandwidth limit",
                                ]
                        }
                },
-               "/api/v1/clients/show": {
+               "/api/v2/clients/show": {
                        "get": {
                                "tags": ["clients"],
                                "summary": "Show clients",
                                ]
                        }
                },
-               "/api/v1/jobs": {
+               "/api/v2/jobs": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Job list",
                                ]
                        }
                },
-               "/api/v1/jobs/{jobid}": {
+               "/api/v2/jobs/{jobid}": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Find job by JobId",
                                ]
                        }
                },
-               "/api/v1/jobs/{jobid}/cancel": {
+               "/api/v2/jobs/{jobid}/cancel": {
                        "put": {
                                "tags": ["jobs"],
                                "summary": "Cancel job",
                                "description": "Cancel running job.",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Cancel command output",
                                ]
                        }
                },
-               "/api/v1/jobs/{jobid}/show": {
+               "/api/v2/jobs/{jobid}/show": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Show job",
                                ]
                        }
                },
-               "/api/v1/jobs/{jobid}/bandwidth": {
+               "/api/v2/jobs/{jobid}/bandwidth": {
                        "put": {
                                "tags": ["jobs"],
                                "summary": "Set Job bandwidth limit",
                                ]
                        }
                },
-               "/api/v1/jobs/{jobid}/files": {
+               "/api/v2/jobs/{jobid}/files": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Show job files and directories",
                                ]
                        }
                },
-               "/api/v1/jobs/files": {
+               "/api/v2/jobs/files": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Search jobs by file criteria",
                                ]
                        }
                },
-               "/api/v1/jobs/resnames": {
+               "/api/v2/jobs/resnames": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Job resource names",
                                ]
                        }
                },
-               "/api/v1/jobs/recent/{jobname}": {
+               "/api/v2/jobs/recent/{jobname}": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Get most recent jobids for job to restore",
                                ]
                        }
                },
-               "/api/v1/jobs/estimate": {
+               "/api/v2/jobs/estimate": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Get estimate output",
                        "post": {
                                "tags": ["jobs"],
                                "summary": "Estimate job bytes and files",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "description": "Estimate job bytes and files before real job run. There can be used (id OR name) and (clientid OR client)",
                                "responses": {
                                        "200": {
                                },
                                "parameters": [
                                        {
-                                               "name": "create[id]",
+                                               "name": "id",
                                                "in": "body",
                                                "description": "Job identifier",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[name]",
+                                               "name": "name",
                                                "in": "body",
                                                "description": "Job name (can be used instead id)",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[clientid]",
+                                               "name": "clientid",
                                                "in": "body",
                                                "description": "Client identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[client]",
+                                               "name": "client",
                                                "in": "body",
                                                "description": "Client name (can be used instead clientid)",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[fileset]",
+                                               "name": "fileset",
                                                "in": "body",
                                                "description": "FileSet name",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[accurate]",
+                                               "name": "accurate",
                                                "in": "body",
                                                "description": "Accurate mode, 1 if enabled, otherwise 0",
                                                "required": false,
                                ]
                        }
                },
-               "/api/v1/jobs/run": {
+               "/api/v2/jobs/run": {
                        "post": {
                                "tags": ["jobs"],
                                "summary": "Run job",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "description": "Run job with specific parameters. There can be used (id OR name) and (clientid OR client)",
                                "responses": {
                                        "200": {
                                },
                                "parameters": [
                                        {
-                                               "name": "create[id]",
+                                               "name": "id",
                                                "in": "body",
                                                "description": "Job identifier",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[name]",
+                                               "name": "name",
                                                "in": "body",
                                                "description": "Job name (can be used instead id)",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[clientid]",
+                                               "name": "level",
+                                               "in": "body",
+                                               "description": "Job level",
+                                               "required": true,
+                                               "schema": {
+                                                       "type": "string",
+                                                       "enum": ["F","I", "D", "B", "f", "V", "C", "O", "d"]
+                                               }
+                                       },
+                                       {
+                                               "name": "clientid",
                                                "in": "body",
                                                "description": "Client identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[client]",
+                                               "name": "client",
                                                "in": "body",
                                                "description": "Client name (can be used instead clientid)",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[storageid]",
+                                               "name": "storageid",
                                                "in": "body",
                                                "description": "Storage identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[storage]",
+                                               "name": "storage",
                                                "in": "body",
                                                "description": "Storage name (can be used instead storageid)",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[poolid]",
+                                               "name": "poolid",
                                                "in": "body",
                                                "description": "Pool identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[pool]",
+                                               "name": "pool",
                                                "in": "body",
                                                "description": "Pool name (can be used instead poolid)",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[filesetid]",
+                                               "name": "filesetid",
                                                "in": "body",
                                                "description": "FileSet identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[fileset]",
+                                               "name": "fileset",
                                                "in": "body",
                                                "description": "FileSet name (can be used instead filesetid)",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[priority]",
+                                               "name": "priority",
                                                "in": "body",
                                                "description": "Job priority",
                                                "required":  false,
                                                }
                                        },
                                        {
-                                               "name": "create[accurate]",
+                                               "name": "accurate",
                                                "in": "body",
                                                "description": "Accurate mode, 1 if enabled, otherwise 0",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[jobid]",
+                                               "name": "jobid",
                                                "in": "body",
                                                "description": "Job identifier for verify job",
                                                "required":  false,
                                                }
                                        },
                                        {
-                                               "name": "create[verifyjob]",
+                                               "name": "verifyjob",
                                                "in": "body",
                                                "description": "Verify job name",
                                                "required":  false,
                                ]
                        }
                },
-               "/api/v1/jobs/show": {
+               "/api/v2/jobs/show": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Show jobs",
                                ]
                        }
                },
-               "/api/v1/jobs/totals": {
+               "/api/v2/jobs/totals": {
                        "get": {
                                "tags": ["jobs"],
                                "summary": "Show job total bytes and files",
                                }
                        }
                },
-               "/api/v1/jobs/restore": {
+               "/api/v2/jobs/restore": {
                        "post": {
                                "tags": ["jobs"],
                                "summary": "Restore job",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "description": "Restore backup job.",
                                "responses": {
                                        "200": {
                                },
                                "parameters": [
                                        {
-                                               "name": "create[id]",
+                                               "name": "id",
                                                "in": "body",
                                                "description": "Job identifier (for full restore)",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[clientid]",
+                                               "name": "clientid",
                                                "in": "body",
                                                "description": "Client identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[client]",
+                                               "name": "client",
                                                "in": "body",
                                                "description": "Client name (can be used instead clientid)",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[where]",
+                                               "name": "where",
                                                "in": "body",
                                                "description": "Where to restore files",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[rpath]",
+                                               "name": "rpath",
                                                "in": "body",
                                                "description": "Rpath (restore path)",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[full]",
+                                               "name": "full",
                                                "in": "body",
                                                "description": "Full restore all files",
                                                "required":  false,
                                                }
                                        },
                                        {
-                                               "name": "create[filesetid]",
+                                               "name": "filesetid",
                                                "in": "body",
                                                "description": "FileSet identifier (for full restore)",
                                                "required":  false,
                                                }
                                        },
                                        {
-                                               "name": "create[fileset]",
+                                               "name": "fileset",
                                                "in": "body",
                                                "description": "FileSet (can be used instead of filesetid) (for full restore)",
                                                "required":  false,
                                                }
                                        },
                                        {
-                                               "name": "create[restorejob]",
+                                               "name": "restorejob",
                                                "in": "body",
                                                "description": "Restore job name",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[strip_prefix]",
+                                               "name": "strip_prefix",
                                                "in": "body",
                                                "description": "Strip prefix in restored paths",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[add_prefix]",
+                                               "name": "add_prefix",
                                                "in": "body",
                                                "description": "Add prefix to restored paths",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[add_suffix]",
+                                               "name": "add_suffix",
                                                "in": "body",
                                                "description": "Add suffix to restored paths",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[regex_where]",
+                                               "name": "regex_where",
                                                "in": "body",
                                                "description": "Use regex to file relocation",
                                                "required": false,
                                ]
                        }
                },
-               "/api/v1/storages": {
+               "/api/v2/storages": {
                        "get": {
                                "tags": ["storages"],
                                "summary": "Storage list",
                                }]
                        }
                },
-               "/api/v1/storages/{storageid}": {
+               "/api/v2/storages/{storageid}": {
                        "get": {
                                "tags": ["storages"],
                                "summary": "Find storage by StorageId",
                                }]
                        }
                },
-               "/api/v1/storages/{storageid}/show": {
+               "/api/v2/storages/{storageid}/show": {
                        "get": {
                                "tags": ["storages"],
                                "summary": "Show storage",
                                ]
                        }
                },
-               "/api/v1/storages/{storageid}/status": {
+               "/api/v2/storages/{storageid}/status": {
                        "get": {
                                "tags": ["storages"],
                                "summary": "Storage status",
                                                                                        "type": "array",
                                                                                        "items": {
                                                                                                "type": "string",
-                                                                                               "description": "Storage status"
+                                                                                               "description": "Storage status output"
                                                                                        }
                                                                                },
                                                                                "error": {
                                                                                        "type": "integer",
                                                                                        "description": "Error code",
-                                                                                       "enum": [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 1000]
+                                                                                       "enum": [0, 1, 2, 3, 4, 5, 6, 7, 11, 20, 1000]
                                                                                }
                                                                        }
                                                                }
                                                }
                                        }
                                },
-                               "parameters": [{
-                                       "$ref": "#/components/parameters/StorageId"
-                               }]
+                               "parameters": [
+                                       {
+                                               "$ref": "#/components/parameters/StorageId"
+                                       },
+                                       {
+                                               "$ref": "#/components/parameters/Output"
+                                       },
+                                       {
+                                               "name": "type",
+                                               "in": "query",
+                                               "description": "Output type using together with output=json parameter.",
+                                               "schema": {
+                                                       "type": "string",
+                                                       "enum": ["header", "running", "devices", "terminated"]
+                                               }
+                                       }
+                               ]
                        }
                },
-               "/api/v1/storages/{storageid}/mount": {
+               "/api/v2/storages/{storageid}/mount": {
                        "get": {
                                "tags": ["storages"],
                                "summary": "Mount storage",
                                ]
                        }
                },
-               "/api/v1/storages/{storageid}/umount": {
+               "/api/v2/storages/{storageid}/umount": {
                        "get": {
                                "tags": ["storages"],
                                "summary": "Umount storage",
                                ]
                        }
                },
-               "/api/v1/storages/{storageid}/release": {
+               "/api/v2/storages/{storageid}/release": {
                        "get": {
                                "tags": ["storages"],
                                "summary": "Release storage",
                                ]
                        }
                },
-               "/api/v1/storages/show": {
+               "/api/v2/storages/show": {
                        "get": {
                                "tags": ["storages"],
                                "summary": "Show storages",
                                ]
                        }
                },
-               "/api/v1/pools": {
+               "/api/v2/pools": {
                        "get": {
                                "tags": ["pools"],
                                "summary": "Pool list",
                                }]
                        }
                },
-               "/api/v1/pools/{poolid}": {
+               "/api/v2/pools/{poolid}": {
                        "get": {
                                "tags": ["pools"],
                                "summary": "Find pool by PoolId",
                                }]
                        }
                },
-               "/api/v1/pools/{poolid}/volumes": {
+               "/api/v2/pools/{poolid}/volumes": {
                        "get": {
                                "tags": ["pools"],
                                "summary": "Get all volumes in pool",
                                }]
                        }
                },
-               "/api/v1/pools/{poolid}/show": {
+               "/api/v2/pools/{poolid}/show": {
                        "get": {
                                "tags": ["pools"],
                                "summary": "Show pool",
                                }]
                        }
                },
-               "/api/v1/pools/{poolid}/update": {
+               "/api/v2/pools/{poolid}/update": {
                        "put": {
                                "tags": ["pools"],
                                "summary": "Update pool properties",
                                "description": "Update properties in specific pool",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Update pool output",
                                }]
                        }
                },
-               "/api/v1/pools/{poolid}/update/volumes": {
+               "/api/v2/pools/{poolid}/update/volumes": {
                        "put": {
                                "tags": ["pools"],
                                "summary": "Update properties all volumes in pool",
                                "description": "Update properties all volumes in specific pool",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Update all volumes in pool output",
                                }]
                        }
                },
-               "/api/v1/pools/show": {
+               "/api/v2/pools/show": {
                        "get": {
                                "tags": ["pools"],
                                "summary": "Show pools",
                                ]
                        }
                },
-               "/api/v1/volumes": {
+               "/api/v2/volumes": {
                        "get": {
                                "tags": ["volumes"],
                                "summary": "Volume list",
                                }]
                        }
                },
-               "/api/v1/volumes/{mediaid}": {
+               "/api/v2/volumes/{mediaid}": {
                        "get": {
                                "tags": ["volumes"],
                                "summary": "Find volume by MediaId",
                                "tags": ["volumes"],
                                "summary": "Update volume properties",
                                "description": "Update specific volume properties.",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": { 
                                                "description": "Update volume output",
                                                "$ref": "#/components/parameters/MediaId"
                                        },
                                        {
-                                               "name": "update[volstatus]",
+                                               "name": "volstatus",
                                                "in": "body",
                                                "description": "Volume status",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[poolid]",
+                                               "name": "poolid",
                                                "in": "body",
                                                "description": "Update Volume Pool by Pool identifier",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[volretention]",
+                                               "name": "volretention",
                                                "in": "body",
                                                "description": "Volume retention time",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[voluseduration]",
+                                               "name": "voluseduration",
                                                "in": "body",
                                                "description": "Volume use duration time",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[maxvoljobs]",
+                                               "name": "maxvoljobs",
                                                "in": "body",
                                                "description": "Maximum volume jobs",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[maxvolfiles]",
+                                               "name": "maxvolfiles",
                                                "in": "body",
                                                "description": "Maximum volume files",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[maxvolbytes]",
+                                               "name": "maxvolbytes",
                                                "in": "body",
                                                "description": "Maximum volume bytes",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[slot]",
+                                               "name": "slot",
                                                "in": "body",
                                                "description": "Volume slot",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[recycle]",
+                                               "name": "recycle",
                                                "in": "body",
                                                "description": "Volume recycle flag",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[enabled]",
+                                               "name": "enabled",
                                                "in": "body",
                                                "description": "Volume enabled flag",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "update[inchanger]",
+                                               "name": "inchanger",
                                                "in": "body",
                                                "description": "Volume InChanger flag",
                                                "required": false,
                                ]
                        }
                },
-               "/api/v1/volumes/{mediaid}/prune": {
+               "/api/v2/volumes/{mediaid}/prune": {
                        "put": {
                                "tags": ["volumes"],
                                "summary": "Prune volume",
                                "description": "Do prunning on volume.",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": { 
                                                "description": "Prune volume output",
                                ]
                        }
                },
-               "/api/v1/volumes/{mediaid}/purge": {
+               "/api/v2/volumes/{mediaid}/purge": {
                        "put": {
                                "tags": ["volumes"],
                                "summary": "Purge volume",
                                "description": "Do purging on volume.",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": { 
                                                "description": "Purge volume output",
                                ]
                        }
                },
-               "/api/v1/volumes/{mediaid}/jobs": {
+               "/api/v2/volumes/{mediaid}/jobs": {
                        "get": {
                                "tags": ["volumes"],
                                "summary": "Jobs on volume",
                                }]
                        }
                },
-               "/api/v1/volumes/required/{jobid}/{fileid}": {
+               "/api/v2/volumes/required/{jobid}/{fileid}": {
                        "get": {
                                "tags": ["volumes"],
                                "summary": "Get volumes required to restore file",
                                ]
                        }
                },
-               "/api/v1/volumes/label": {
+               "/api/v2/volumes/label": {
                        "get": {
                                "tags": ["volumes"],
                                "summary": "Get label volume output",
                                "tags": ["volumes"],
                                "summary": "Label volume",
                                "description": "Label volume with specified name (without using barcode).",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Label volume with specified name (without barcode)",
                                },
                                "parameters": [
                                        {
-                                               "name": "create[volume]",
+                                               "name": "volume",
                                                "in": "body",
                                                "description": "Volume name",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[slot]",
+                                               "name": "slot",
                                                "in": "body",
                                                "description": "Slot number",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[drive]",
+                                               "name": "drive",
                                                "in": "body",
                                                "description": "Drive number",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[storageid]",
+                                               "name": "storageid",
                                                "in": "body",
                                                "description": "Storage identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[storage]",
+                                               "name": "storage",
                                                "in": "body",
                                                "description": "Storage name can be used instead of storageid",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[poolid]",
+                                               "name": "poolid",
                                                "in": "body",
                                                "description": "Pool identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[pool]",
+                                               "name": "pool",
                                                "in": "body",
                                                "description": "Pool name can be used instead of poolid",
                                                "required":  true,
                                ]
                        }
                },
-               "/api/v1/volumes/label/barcodes": {
+               "/api/v2/volumes/label/barcodes": {
                        "get": {
                                "tags": ["volumes"],
                                "summary": "Get label barcodes volume output",
                                "tags": ["volumes"],
                                "summary": "Label volume using barcodes",
                                "description": "Label volume with specified name (with using barcode).",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Label volume with specified name (with barcode)",
                                },
                                "parameters": [
                                        {
-                                               "name": "create[slots]",
+                                               "name": "slots",
                                                "in": "body",
                                                "description": "Slots numbers or slots range (ex. 1-3,5,10)",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[drive]",
+                                               "name": "drive",
                                                "in": "body",
                                                "description": "Drive number",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[storageid]",
+                                               "name": "storageid",
                                                "in": "body",
                                                "description": "Storage identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[storage]",
+                                               "name": "storage",
                                                "in": "body",
                                                "description": "Storage name can be used instead of storageid",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[poolid]",
+                                               "name": "poolid",
                                                "in": "body",
                                                "description": "Pool identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "create[pool]",
+                                               "name": "pool",
                                                "in": "body",
                                                "description": "Pool name can be used instead of poolid",
                                                "required":  true,
                                ]
                        }
                },
-               "/api/v1/volumes/update": {
+               "/api/v2/volumes/update": {
                        "get": {
                                "tags": ["volumes"],
                                "summary": "Get update slots output",
                                "tags": ["volumes"],
                                "summary": "Update slots",
                                "description": "Update volume slots (without using barcode).",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Update volume slots (without barcode)",
                                },
                                "parameters": [
                                        {
-                                               "name": "update[slots]",
+                                               "name": "slots",
                                                "in": "body",
                                                "description": "Slots numbers or slots range (ex. 1-3,5,10)",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "update[drive]",
+                                               "name": "drive",
                                                "in": "body",
                                                "description": "Drive number",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "update[storageid]",
+                                               "name": "storageid",
                                                "in": "body",
                                                "description": "Storage identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "update[storage]",
+                                               "name": "storage",
                                                "in": "body",
                                                "description": "Storage name can be used instead of storageid",
                                                "required":  true,
                                ]
                        }
                },
-               "/api/v1/volumes/update/barcodes": {
+               "/api/v2/volumes/update/barcodes": {
                        "get": {
                                "tags": ["volumes"],
                                "summary": "Get update slots output using barcodes",
                                "tags": ["volumes"],
                                "summary": "Update slots using barcodes",
                                "description": "Update volume slots (with using barcode).",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Update volume slots (with barcode)",
                                },
                                "parameters": [
                                        {
-                                               "name": "update[slots]",
+                                               "name": "slots",
                                                "in": "body",
                                                "description": "Slots numbers or slots range (ex. 1-3,5,10)",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "update[drive]",
+                                               "name": "drive",
                                                "in": "body",
                                                "description": "Drive number",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "update[storageid]",
+                                               "name": "storageid",
                                                "in": "body",
                                                "description": "Storage identifier",
                                                "required":  true,
                                                }
                                        },
                                        {
-                                               "name": "update[storage]",
+                                               "name": "storage",
                                                "in": "body",
                                                "description": "Storage name can be used instead of storageid",
                                                "required":  true,
                                ]
                        }
                },
-               "/api/v1/filesets": {
+               "/api/v2/filesets": {
                        "get": {
                                "tags": ["filesets"],
                                "summary": "FileSet list",
                                }]
                        }
                },
-               "/api/v1/filesets/{filesetid}": {
+               "/api/v2/filesets/{filesetid}": {
                        "get": {
                                "tags": ["filesets"],
                                "summary": "Find FileSet by FileSetId",
                                }]
                        }
                },
-               "/api/v1/filesets/resnames": {
+               "/api/v2/filesets/resnames": {
                        "get": {
                                "tags": ["filesets"],
                                "summary": "FileSet resource names",
                                }
                        }
                },
-               "/api/v1/schedules/resnames": {
+               "/api/v2/schedules/resnames": {
                        "get": {
                                "tags": ["schedules"],
                                "summary": "Schedule resource names",
                                }
                        }
                },
-               "/api/v1/schedules/status": {
+               "/api/v2/schedules/status": {
                        "get": {
                                "tags": ["schedules"],
                                "summary": "Schedule status",
                                ]
                        }
                },
-               "/api/v1/bvfs/update": {
+               "/api/v2/bvfs/update": {
                        "put": {
                                "tags": ["bvfs"],
                                "summary": "Update BVFS cache",
                                "description": "Update BVFS cache for specific jobs identifiers",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "BVFS update command output",
                                }
                        }
                },
-               "/api/v1/bvfs/lsdirs": {
+               "/api/v2/bvfs/lsdirs": {
                        "get": {
                                "tags": ["bvfs"],
                                "summary": "BVFS list directories",
                                ]
                        }
                },
-               "/api/v1/bvfs/lsfiles": {
+               "/api/v2/bvfs/lsfiles": {
                        "get": {
                                "tags": ["bvfs"],
                                "summary": "BVFS list files",
                                ]
                        }
                },
-               "/api/v1/bvfs/versions": {
+               "/api/v2/bvfs/versions": {
                        "get": {
                                "tags": ["bvfs"],
                                "summary": "BVFS list file versions",
                                ]
                        }
                },
-               "/api/v1/bvfs/getjobids": {
+               "/api/v2/bvfs/getjobids": {
                        "get": {
                                "tags": ["bvfs"],
                                "summary": "BVFS get particular jobids to restore",
                                ]
                        }
                },
-               "/api/v1/bvfs/restore": {
+               "/api/v2/bvfs/restore": {
                        "post": {
                                "tags": ["bvfs"],
                                "summary": "Prepare BVFS restore",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "description": "Prepare BVFS restore",
                                "responses": {
                                        "200": {
                                },
                                "parameters": [
                                        {
-                                               "name": "create[path]",
+                                               "name": "path",
                                                "in": "body",
                                                "description": "Path in format b2[0-9]+",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[jobids]",
+                                               "name": "jobids",
                                                "in": "body",
                                                "description": "Comma separated job identifiers",
                                                "required": true,
                                                }
                                        },
                                        {
-                                               "name": "create[fileid]",
+                                               "name": "fileid",
                                                "in": "body",
                                                "description": "Comma seprated file identifiers",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[dirid]",
+                                               "name": "dirid",
                                                "in": "body",
                                                "description": "Comma seprated directory identifiers",
                                                "required": false,
                                                }
                                        },
                                        {
-                                               "name": "create[findex]",
+                                               "name": "findex",
                                                "in": "body",
                                                "description": "Comma seprated directory file indexes",
                                                "required": false,
                                ]
                        }
                },
-               "/api/v1/bvfs/clear": {
+               "/api/v2/bvfs/clear": {
                        "put": {
                                "tags": ["bvfs"],
                                "summary": "Clear BVFS cache",
                                "description": "Clear BVFS cache",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "BVFS clear cache command output",
                                }
                        }
                },
-               "/api/v1/bvfs/cleanup": {
+               "/api/v2/bvfs/cleanup": {
                        "put": {
                                "tags": ["bvfs"],
                                "summary": "Cleanup BVFS (remove temporary table)",
                                "description": "Cleanup BVFS",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "BVFS cleanup output",
                                },
                                "parameters": [
                                        {
-                                               "name": "update[path]",
+                                               "name": "path",
                                                "in": "body",
                                                "description": "Path in format b2[0-9]+",
                                                "required": true,
                                ]
                        }
                },
-               "/api/v1/config": {
+               "/api/v2/config": {
                        "get": {
                                "tags": ["config"],
                                "summary": "Get components information",
                                }
                        }
                },
-               "/api/v1/config/{component_type}": {
+               "/api/v2/config/{component_type}": {
                        "get": {
                                "tags": ["config"],
                                "summary": "Get component config",
                                "tags": ["config"],
                                "summary": "Set component config",
                                "description": "Set specific component config",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Set config status",
                                                "$ref": "#/components/parameters/ComponentType"
                                        },
                                        {
-                                               "name": "update[config]",
+                                               "name": "config",
                                                "in": "body",
                                                "description": "Config in JSON form to set",
                                                "required": true,
                                ]
                        }
                },
-               "/api/v1/config/{component_type}/{resource_type}": {
+               "/api/v2/config/{component_type}/{resource_type}": {
                        "get": {
                                "tags": ["config"],
                                "summary": "Get component resources config",
                                "tags": ["config"],
                                "summary": "Set component resources config",
                                "description": "Set specific component resources config",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Set resources config",
                                                "$ref": "#/components/parameters/ResourceType"
                                        },
                                        {
-                                               "name": "update[config]",
+                                               "name": "config",
                                                "in": "body",
                                                "description": "Config in JSON form to set",
                                                "required": true,
                                ]
                        }
                },
-               "/api/v1/config/{component_type}/{resource_type}/{resource_name}": {
+               "/api/v2/config/{component_type}/{resource_type}/{resource_name}": {
                        "get": {
                                "tags": ["config"],
                                "summary": "Get component resource config",
                                "tags": ["config"],
                                "summary": "Set component resource config",
                                "description": "Set specific component resource config",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Set single resource config",
                                                "$ref": "#/components/parameters/ResourceName"
                                        },
                                        {
-                                               "name": "update[config]",
+                                               "name": "config",
                                                "in": "body",
                                                "description": "Config in JSON form to set",
                                                "required": true,
                                ]
                        }
                },
-               "/api/v1/joblog/{jobid}": {
+               "/api/v2/joblog/{jobid}": {
                        "get": {
                                "tags": ["joblog"],
                                "summary": "Get job log for jobid",
                                }]
                        }
                },
-               "/api/v1/joblog/messages": {
+               "/api/v2/joblog/messages": {
                        "get": {
                                "tags": ["joblog"],
                                "summary": "Get console messages log.",
                                }]
                        }
                },
-               "/api/v1/status/director": {
+               "/api/v2/directors/{director_name}/status": {
                        "get": {
-                               "tags": ["status"],
+                               "tags": ["directors"],
                                "summary": "Get director status",
                                "description": "Get director status",
                                "responses": {
                                },
                                "parameters": [
                                        {
-                                               "name": "type",
-                                               "in": "query",
-                                               "description": "Output type (header, scheduled, running, terminated)",
-                                               "schema": {
-                                                       "type": "string"
-                                               }
-                                       }
-                               ]
-                       }
-               },
-               "/api/v1/status/storage": {
-                       "get": {
-                               "tags": ["status"],
-                               "summary": "Get storage status",
-                               "description": "Get storage status",
-                               "responses": {
-                                       "200": {
-                                               "description": "Storage status output",
-                                               "content": {
-                                                       "application/json": {
-                                                               "schema": {
-                                                                       "type": "object",
-                                                                       "properties": {
-                                                                               "output": {
-                                                                                       "description": "Storage status output",
-                                                                                       "type": "object",
-                                                                                       "properties": {
-                                                                                               "header": {
-                                                                                                       "description": "Status header",
-                                                                                                       "type": "array",
-                                                                                                       "items": {
-                                                                                                               "type": "object",
-                                                                                                               "properties": {
-                                                                                                                       "name": {
-                                                                                                                               "description": "Storage name",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "version": {
-                                                                                                                               "description": "Storage version",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "uname": {
-                                                                                                                               "description": "Storage uname",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "started_epoch": {
-                                                                                                                               "description": "Started epoch",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "started": {
-                                                                                                                               "description": "Started time",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobs_run": {
-                                                                                                                               "description": "Jobs run",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobs_running": {
-                                                                                                                               "description": "Jobs running",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "ndevices": {
-                                                                                                                               "description": "Number of devices",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "nautochgr": {
-                                                                                                                               "description": "Number of autochangers",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "plugins": {
-                                                                                                                               "description": "Plugins",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "drivers": {
-                                                                                                                               "description": "Drivers",
-                                                                                                                               "type": "string"
-                                                                                                                       }
-                                                                                                               }
-                                                                                                       }
-                                                                                               },
-                                                                                               "devices": {
-                                                                                                       "description": "Storage devices",
-                                                                                                       "type": "array",
-                                                                                                       "items": {
-                                                                                                               "type": "object",
-                                                                                                               "properties": {
-                                                                                                                       "name": {
-                                                                                                                               "description": "Device name",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "archive_device": {
-                                                                                                                               "description": "Archive device",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "type": {
-                                                                                                                               "description": "Device type",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "media_type": {
-                                                                                                                               "description": "Media type",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "open": {
-                                                                                                                               "description": "Open",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "writers": {
-                                                                                                                               "description": "Writers",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "maximum_concurrent_jobs": {
-                                                                                                                               "description": "Maximum concurrent jobs",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "maximum_volume_size": {
-                                                                                                                               "description": "Maximum volume size",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "read_only": {
-                                                                                                                               "description": "Read only",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "autoselect": {
-                                                                                                                               "description": "Auto select",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "enabled": {
-                                                                                                                               "description": "Enabled",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "free_space": {
-                                                                                                                               "description": "Free space",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "total_space": {
-                                                                                                                               "description": "Total space",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "devno": {
-                                                                                                                               "description": "Device number",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "mounted": {
-                                                                                                                               "description": "Mounted",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "waiting": {
-                                                                                                                               "description": "Waiting",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "volume": {
-                                                                                                                               "description": "Volume name",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "pool": {
-                                                                                                                               "description": "Pool name",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "blocked_desc": {
-                                                                                                                               "description": "Blocked description",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "blocked": {
-                                                                                                                               "description": "Blocked",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "append": {
-                                                                                                                               "description": "Append",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "bytes": {
-                                                                                                                               "description": "Bytes",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "blocks": {
-                                                                                                                               "description": "Blocks",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "file": {
-                                                                                                                               "description": "File",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "block": {
-                                                                                                                               "description": "Block",
-                                                                                                                               "type": "string"
-                                                                                                                       }
-                                                                                                               }
-                                                                                                       }
-                                                                                               },
-                                                                                               "running": {
-                                                                                                       "description": "Running jobs",
-                                                                                                       "type": "array",
-                                                                                                       "items": {
-                                                                                                               "type": "object",
-                                                                                                               "properties": {
-                                                                                                                       "jobid": {
-                                                                                                                               "description": "Job identifier",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "job": {
-                                                                                                                               "description": "Job uname",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "level": {
-                                                                                                                               "description": "Job level",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "type": {
-                                                                                                                               "description": "Job type",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "status": {
-                                                                                                                               "description": "Job status letter",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "status_desc": {
-                                                                                                                               "description": "Status description",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobbytes": {
-                                                                                                                               "description": "jobbytes",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobfiles": {
-                                                                                                                               "description": "Job files",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "starttime_epoch": {
-                                                                                                                               "description": "Start time epoch",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "starttime": {
-                                                                                                                               "description": "Start time",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "errors": {
-                                                                                                                               "description": "Errors",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "newbsr": {
-                                                                                                                               "description": "New BSR",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "read_volume": {
-                                                                                                                               "description": "Read volume",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "read_pool": {
-                                                                                                                               "description": "Read pool",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "read_device": {
-                                                                                                                               "description": "Read device",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "write_volume": {
-                                                                                                                               "description": "Write volume",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "write_pool": {
-                                                                                                                               "description": "Write pool",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "write_device": {
-                                                                                                                               "description": "Write device",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "spooling": {
-                                                                                                                               "description": "Spooling",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "despooling": {
-                                                                                                                               "description": "Despooling",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "despool_wait": {
-                                                                                                                               "description": "Despool wait",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "avebytes_sec": {
-                                                                                                                               "description": "Averate bytes/second",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "lastbytes_sec": {
-                                                                                                                               "description": "Last bytes/second",
-                                                                                                                               "type": "string"
-                                                                                                                       }
-                                                                                                               }
-                                                                                                       }
-                                                                                               },
-                                                                                               "terminated": {
-                                                                                                       "description": "Terminated jobs",
-                                                                                                       "type": "array",
-                                                                                                       "items": {
-                                                                                                               "type": "object",
-                                                                                                               "properties": {
-                                                                                                                       "jobid": {
-                                                                                                                               "description": "Job identifier",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "level": {
-                                                                                                                               "description": "Job level",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "type": {
-                                                                                                                               "description": "Job type",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "status": {
-                                                                                                                               "description": "Job status letter",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "status_desc": {
-                                                                                                                               "description": "Status description",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobbytes": {
-                                                                                                                               "description": "jobbytes",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobfiles": {
-                                                                                                                               "description": "Job files",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "job": {
-                                                                                                                               "description": "Job uname",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "name": {
-                                                                                                                               "description": "Job name",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "starttime_epoch": {
-                                                                                                                               "description": "Start time epoch",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "starttime": {
-                                                                                                                               "description": "Start time",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "endtime_epoch": {
-                                                                                                                               "description": "End time epoch",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "endtime": {
-                                                                                                                               "description": "End time",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "errors": {
-                                                                                                                               "description": "Errors",
-                                                                                                                               "type": "string"
-                                                                                                                       }
-                                                                                                               }
-                                                                                                       }
-                                                                                               }
-                                                                                       }
-                                                                               },
-                                                                               "error": {
-                                                                                       "type": "integer",
-                                                                                       "description": "Error code",
-                                                                                       "enum": [0, 1, 2, 3, 4, 5, 6, 7, 11, 1000]
-                                                                               }
-                                                                       }
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               },
-                               "parameters": [
-                                       {
-                                               "name": "name",
-                                               "in": "query",
-                                               "description": "Storage name to get status",
+                                               "name": "director_name",
+                                               "in": "path",
+                                               "description": "Director name",
+                                               "required": true,
                                                "schema": {
                                                        "type": "string"
                                                }
                                        },
                                        {
-                                               "name": "type",
-                                               "in": "query",
-                                               "description": "Output type (header, devices, running, terminated)",
-                                               "schema": {
-                                                       "type": "string"
-                                               }
-                                       }
-                               ]
-                       }
-               },
-               "/api/v1/status/client": {
-                       "get": {
-                               "tags": ["status"],
-                               "summary": "Get client status",
-                               "description": "Get client status",
-                               "responses": {
-                                       "200": {
-                                               "description": "Client status output",
-                                               "content": {
-                                                       "application/json": {
-                                                               "schema": {
-                                                                       "type": "object",
-                                                                       "properties": {
-                                                                               "output": {
-                                                                                       "description": "Client status output",
-                                                                                       "type": "object",
-                                                                                       "properties": {
-                                                                                               "header": {
-                                                                                                       "description": "Status header",
-                                                                                                       "type": "array",
-                                                                                                       "items": {
-                                                                                                               "type": "object",
-                                                                                                               "properties": {
-                                                                                                                       "name": {
-                                                                                                                               "description": "Client name",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "version": {
-                                                                                                                               "description": "Client version",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "uname": {
-                                                                                                                               "description": "Client uname",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "started_epoch": {
-                                                                                                                               "description": "Started epoch",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "started": {
-                                                                                                                               "description": "Started time",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobs_run": {
-                                                                                                                               "description": "Jobs run",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobs_running": {
-                                                                                                                               "description": "Jobs running",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "winver": {
-                                                                                                                               "description": "Winver",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "debug": {
-                                                                                                                               "description": "Debug state",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "trace": {
-                                                                                                                               "description": "Trace state",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "bwlimit": {
-                                                                                                                               "description": "Bandwidth limit",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "plugins": {
-                                                                                                                               "description": "Plugins",
-                                                                                                                               "type": "string"
-                                                                                                                       }
-                                                                                                               }
-                                                                                                       }
-                                                                                               },
-                                                                                               "running": {
-                                                                                                       "description": "Running jobs",
-                                                                                                       "type": "array",
-                                                                                                       "items": {
-                                                                                                               "type": "object",
-                                                                                                               "properties": {
-                                                                                                                       "jobid": {
-                                                                                                                               "description": "Job identifier",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "job": {
-                                                                                                                               "description": "Job uname",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "vss": {
-                                                                                                                               "description": "VSS state",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "level": {
-                                                                                                                               "description": "Job level",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "type": {
-                                                                                                                               "description": "Job type",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "status": {
-                                                                                                                               "description": "Job status letter",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobbytes": {
-                                                                                                                               "description": "jobbytes",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobfiles": {
-                                                                                                                               "description": "Job files",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "starttime_epoch": {
-                                                                                                                               "description": "Start time epoch",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "starttime": {
-                                                                                                                               "description": "Start time",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "errors": {
-                                                                                                                               "description": "Errors",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "bytes_sec": {
-                                                                                                                               "description": "Averate bytes/second",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "readbytes": {
-                                                                                                                               "description": "Bytes read",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "files_examined": {
-                                                                                                                               "description": "Examined files count",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "processing_file": {
-                                                                                                                               "description": "Processing file",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "sdreadseqno": {
-                                                                                                                               "description": "SD read seq number",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "fd": {
-                                                                                                                               "description": "FD",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "bwlimit": {
-                                                                                                                               "description": "Bandwidth limit",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "sdtls": {
-                                                                                                                               "description": "SD Tls option",
-                                                                                                                               "type": "string"
-                                                                                                                       }
-                                                                                                               }
-                                                                                                       }
-                                                                                               },
-                                                                                               "terminated": {
-                                                                                                       "description": "Terminated jobs",
-                                                                                                       "type": "array",
-                                                                                                       "items": {
-                                                                                                               "type": "object",
-                                                                                                               "properties": {
-                                                                                                                       "jobid": {
-                                                                                                                               "description": "Job identifier",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "level": {
-                                                                                                                               "description": "Job level",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "type": {
-                                                                                                                               "description": "Job type",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "status": {
-                                                                                                                               "description": "Job status letter",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "status_desc": {
-                                                                                                                               "description": "Status description",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobbytes": {
-                                                                                                                               "description": "Job bytes",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "jobfiles": {
-                                                                                                                               "description": "Job files",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "job": {
-                                                                                                                               "description": "Job uname",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "name": {
-                                                                                                                               "description": "Job name",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "starttime_epoch": {
-                                                                                                                               "description": "Start time epoch",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "starttime": {
-                                                                                                                               "description": "Start time",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "endtime_epoch": {
-                                                                                                                               "description": "End time epoch",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "endtime": {
-                                                                                                                               "description": "End time",
-                                                                                                                               "type": "string"
-                                                                                                                       },
-                                                                                                                       "errors": {
-                                                                                                                               "description": "Errors",
-                                                                                                                               "type": "string"
-                                                                                                                       }
-                                                                                                               }
-                                                                                                       }
-                                                                                               }
-                                                                                       }
-                                                                               },
-                                                                               "error": {
-                                                                                       "type": "integer",
-                                                                                       "description": "Error code",
-                                                                                       "enum": [0, 1, 2, 3, 4, 5, 6, 7, 11, 1000]
-                                                                               }
-                                                                       }
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               },
-                               "parameters": [
-                                       {
-                                               "name": "name",
-                                               "in": "query",
-                                               "description": "Client name to get status",
-                                               "schema": {
-                                                       "type": "string"
-                                               }
+                                               "$ref": "#/components/parameters/Output"
                                        },
                                        {
                                                "name": "type",
                                                "in": "query",
-                                               "description": "Output type (header, running, terminated)",
+                                               "description": "Output type using together with output=json parameter.",
                                                "schema": {
-                                                       "type": "string"
+                                                       "type": "string",
+                                                       "enum": ["header", "scheduled", "running", "terminated"]
                                                }
                                        }
                                ]
                        }
                },
-               "/api/v1/oauth2/clients": {
+               "/api/v2/oauth2/clients": {
                        "get": {
                                "tags": ["oauth2"],
                                "summary": "OAuth2 client account list",
                                }
                        }
                },
-               "/api/v1/oauth2/clients/{client_id}": {
+               "/api/v2/oauth2/clients/{client_id}": {
                        "get": {
                                "tags": ["oauth2"],
                                "summary": "Specific OAuth2 client account config",
                                "tags": ["oauth2"],
                                "summary": "Set OAuth2 client settings",
                                "description": "Set specific OAuth2 client settings",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Set OAuth2 client settings",
                                "tags": ["oauth2"],
                                "summary": "Create OAuth2 client settings",
                                "description": "Create specific OAuth2 client settings",
-                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "consumes": [ "application/json" ],
                                "responses": {
                                        "200": {
                                                "description": "Create OAuth2 client settings",
index ac38351f504aed5371b2ad99d9d52778fef3cc94..0c5988c298a1342b7efcc571a12f50b5e0d9057a 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
@@ -32,6 +32,11 @@ Prado::using('Application.Common.Class.Interfaces');
  */
 class AuthBasic extends AuthBase implements AuthModule {
 
+       /**
+        * Generic name (used e.g. in config files).
+        */
+       const NAME = 'basic';
+
        /**
         * Request header value pattern.
         */
index 416eae05c825ddbb8747d3d16dd92af79c7a0f18..d77ac5094d077e8bc802af3076a1504c4c00762c 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
@@ -33,6 +33,11 @@ Prado::using('Application.Common.Class.OAuth2');
  */
 class AuthOAuth2 extends AuthBase implements AuthModule {
 
+       /**
+        * Generic name (used e.g. in config files).
+        */
+       const NAME = 'oauth2';
+
        /**
         * Get auth type.
         *
index 5de8b14d596e8edf11818d105f5983d8712fb478..dc667c763ce3d460f274f907958b617fe91802ac 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
@@ -31,7 +31,7 @@ Prado::using('System.Web.UI.WebControls.TClientScript');
  */
 class BClientScript extends TClientScript {
 
-       const SCRIPTS_VERSION = 16;
+       const SCRIPTS_VERSION = 17;
 
        public function getScriptUrl()
        {
index 0c54fd9b912902cd18672603e486e17c35ba60d7..af774c75a79eb605c8a9804b2839e12013110b9a 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
@@ -101,8 +101,7 @@ class BaculumUrlMapping extends TUrlMapping {
        }
 
        private function getRequestedUrl() {
-               $url = $this->getRequest()->getPathInfo();
-               return $url;
+               return $this->getRequest()->getPathInfo();
        }
 }
 ?>
diff --git a/gui/baculum/protected/Common/JavaScript/misc.js b/gui/baculum/protected/Common/JavaScript/misc.js
new file mode 100644 (file)
index 0000000..73f9ae3
--- /dev/null
@@ -0,0 +1,236 @@
+var Cookies = {
+       default_exipration_time: 31536000000, // 1 year in miliseconds
+       set_cookie: function(name, value, expiration) {
+               var date = new Date();
+               date.setTime(date.getTime() + this.default_exipration_time);
+               var expires = 'expires=' + date.toUTCString();
+               document.cookie = name + '=' + value + '; ' + expires;
+       },
+       get_cookie: function(name) {
+               name += '=';
+               var values = document.cookie.split(';');
+               var cookie_val = null;
+               var value;
+               for (var i = 0; i < values.length; i++) {
+                       value = values[i];
+                       while (value.charAt(0) == ' ') {
+                               value = value.substr(1);
+                       }
+                       if (value.indexOf(name) == 0) {
+                               cookie_val = value.substring(name.length, value.length);
+                               break;
+                       }
+               }
+               return cookie_val;
+       }
+}
+
+var W3TabsCommon = {
+       open: function(btn_id, item_id) {
+               var tab_items = document.getElementsByClassName(this.css.tab_item);
+               for (var i = 0; i < tab_items.length; i++) {
+                       if (tab_items[i].id === item_id) {
+                               tab_items[i].style.display = 'block';
+                       } else {
+                               tab_items[i].style.display = 'none';
+                       }
+               }
+               var tab_btns = document.getElementsByClassName(this.css.tab_btn);
+               for (var i = 0; i < tab_btns.length; i++) {
+                       if (tab_btns[i].id === btn_id && !tab_btns[i].classList.contains(this.css.tab_item_hover)) {
+                               tab_btns[i].classList.add(this.css.tab_item_hover);
+                       } else if (tab_btns[i].id !== btn_id && tab_btns[i].classList.contains(this.css.tab_item_hover)) {
+                               tab_btns[i].classList.remove(this.css.tab_item_hover);
+                       }
+               }
+       },
+       is_open: function(item_id) {
+               var display = document.getElementById(item_id).style.display;
+               return (display === 'block' || display === '');
+       }
+};
+
+var W3Tabs = {
+       css: {
+               tab_btn: 'tab_btn',
+               tab_item: 'tab_item',
+               tab_item_hover: 'w3-grey'
+       },
+       open: function(btn_id, item_id) {
+               W3TabsCommon.open.call(this, btn_id, item_id);
+       },
+       is_open: function(item_id) {
+               return W3TabsCommon.is_open(item_id);
+       }
+};
+
+var W3SubTabs = {
+       css: {
+               tab_btn: 'subtab_btn',
+               tab_item: 'subtab_item',
+               tab_item_hover: 'w3-border-red'
+       },
+       open: function(btn_id, item_id) {
+               W3TabsCommon.open.call(this, btn_id, item_id);
+       }
+};
+
+var W3SideBar = {
+       ids: {
+               sidebar: 'sidebar',
+               overlay_bg: 'overlay_bg'
+       },
+       css: {
+               page_main: '.page_main_el'
+       },
+       cookies: {
+               side_bar_hide: 'baculum_side_bar_hide'
+       },
+       init: function() {
+               this.sidebar = document.getElementById(this.ids.sidebar);
+               if (!this.sidebar) {
+                       // don't initialize for pages without sidebar
+                       return;
+               }
+               this.overlay_bg = document.getElementById(this.ids.overlay_bg);
+               this.page_main = $(this.css.page_main);
+               var hide = Cookies.get_cookie(this.cookies.side_bar_hide);
+               if (hide == 1) {
+                       this.close();
+               }
+               if (!this.sidebar) {
+                       // on pages without sidebar always show page main elements with 100% width.
+                       this.page_main.css({'margin-left': '0', 'width': '100%'});
+               }
+               this.set_events();
+       },
+       set_events: function() {
+               if (this.sidebar) {
+                       this.sidebar.addEventListener('touchstart', handle_touch_start);
+                       this.sidebar.addEventListener('touchmove', function(e) {
+                               handle_touch_move(e, {
+                                       'swipe_left': function() {
+                                               this.close();
+                                       }.bind(this)
+                               });
+                       }.bind(this));
+               }
+       },
+       open: function() {
+               if (this.sidebar.style.display === 'block' || this.sidebar.style.display === '') {
+                       this.close();
+               } else {
+                       Cookies.set_cookie('baculum_side_bar_hide', 0);
+                       this.sidebar.style.display = 'block';
+                       this.overlay_bg.style.display = 'block';
+                       this.page_main.css({'margin-left': '250px', 'width': 'calc(100% - 250px)'});
+               }
+       },
+       close: function() {
+               Cookies.set_cookie('baculum_side_bar_hide', 1);
+               this.sidebar.style.display = 'none';
+               this.overlay_bg.style.display = 'none';
+               this.page_main.css({'margin-left': '0', 'width': '100%'});
+       }
+};
+
+var touch_start_x = null;
+var touch_start_y = null;
+
+function handle_touch_start(e) {
+       // browser API or jQuery
+       var first_touch =  e.touches || e.originalEvent.touches
+       touch_start_x = first_touch[0].clientX;
+       touch_start_y = first_touch[0].clientY;
+}
+
+function handle_touch_move(e, callbacks) {
+       if (!touch_start_x || !touch_start_y || typeof(callbacks) !== 'object') {
+               // no touch type event or no callbacks
+               return;
+       }
+
+       var touch_end_x = e.touches[0].clientX;
+       var touch_end_y = e.touches[0].clientY;
+
+       var touch_diff_x = touch_start_x - touch_end_x;
+       var touch_diff_y = touch_start_y - touch_end_y;
+
+       if (Math.abs(touch_diff_x) > Math.abs(touch_diff_y)) {
+               if (touch_diff_x > 0 && callbacks.hasOwnProperty('swipe_left')) {
+                       // left swipe
+                       callbacks.swipe_left();
+               } else if (callbacks.hasOwnProperty('swipe_right')) {
+                       // right swipe
+                       callbacks.swipe_right();
+               }
+       } else {
+               if (touch_diff_y > 0 && callbacks.hasOwnProperty('swipe_up')) {
+                       // up swipe
+                       callbacks.swipe_up()
+               } else if (callbacks.hasOwnProperty('swipe_down')) {
+                       // down swipe
+                       callbacks.swipe_down()
+               }
+       }
+
+       // reset values
+       touch_start_x = null;
+       touch_start_y = null;
+}
+
+function set_global_listeners() {
+       document.addEventListener('keydown', function(e) {
+               var key_code = e.keyCode || e.which;
+               switch (key_code) {
+                       case 27: { // escape
+                               $('.w3-modal').filter(':visible').hide(); // hide modals
+                               break;
+                       }
+               }
+       });
+}
+
+
+var get_random_string = function(allowed, len) {
+       var random_string = '';
+       for(var i = 0; i < len; i++) {
+               random_string += allowed.charAt(Math.floor(Math.random() * allowed.length));
+       }
+       return random_string;
+}
+
+var OAuth2Scopes = [
+       'console',
+       'jobs',
+       'directors',
+       'clients',
+       'storages',
+       'volumes',
+       'pools',
+       'bvfs',
+       'joblog',
+       'filesets',
+       'schedules',
+       'config',
+       'actions',
+       'oauth2'
+];
+var set_scopes = function(field_id) {
+       document.getElementById(field_id).value = OAuth2Scopes.join(' ');
+}
+
+function copy_to_clipboard(text) {
+       var input = document.createElement('INPUT');
+       document.body.appendChild(input);
+       input.value = text;
+       input.focus();
+       input.setSelectionRange(0, text.length);
+       document.execCommand('copy');
+       document.body.removeChild(input);
+}
+
+$(function() {
+       W3SideBar.init();
+       set_global_listeners();
+});
index 212300ad3710873cd03ed9494c76c243c4ddabd6..409fdd017ab97cd45524eeefd591918b931d81e3 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
  * Bacula(R) is a registered trademark of Kern Sibbald.
  */
 
+Prado::using('System.Web.UI.ActiveControls.TActiveHiddenField');
 Prado::using('System.Web.UI.ActiveControls.TActiveLabel');
+Prado::using('System.Web.UI.ActiveControls.TActiveLinkButton');
 Prado::using('System.Web.UI.ActiveControls.TActiveButton');
 Prado::using('System.Web.UI.ActiveControls.TActiveTextBox');
 Prado::using('System.Web.UI.TTemplateControl');
 Prado::using('Application.Common.Class.OAuth2');
+Prado::using('Application.Common.Class.BasicUserConfig');
 Prado::using('Application.Common.Portlets.PortletTemplate');
 
 /**
@@ -38,52 +41,102 @@ class NewAuthClient extends PortletTemplate {
 
        private $show_buttons = true;
 
-       private $auth_types = array('basic', 'oauth2');
+       private $auth_types = ['basic', 'oauth2'];
 
        private $auth_type;
 
-       public function addNewAuthClient($sender, $param) {
-               $this->NewAuthClientAddOk->Display = 'None';
-               $this->NewAuthClientAddError->Display = 'None';
-               $this->NewAuthClientAddExists->Display = 'None';
+       private $modes = ['add', 'edit'];
+
+       // @TODO: Move it to a common class
+       const AUTH_TYPE_BASIC = 'basic';
+       const AUTH_TYPE_OAUTH2 = 'oauth2';
+
+       const MODE_TYPE_ADD = 'add';
+       const MODE_TYPE_EDIT = 'edit';
+
+       const MODE = 'Mode';
+
+       public function saveNewAuthClient($sender, $param) {
+               $this->NewAuthClientError->Display = 'None';
+               $this->NewAuthClientExists->Display = 'None';
 
                $result = false;
                $exists = false;
                $config = $this->getModule('api_config')->getConfig();
-               if ($this->getAuthType() === 'basic') {
-                       $users = $this->getModule('basic_apiuser')->getUsers();
-                       if (!key_exists($this->APIBasicLogin->Text, $users)) {
+               if ($this->getAuthType() === self::AUTH_TYPE_BASIC) {
+                       if ($this->Mode == self::MODE_TYPE_ADD) {
+                               $users = $this->getModule('basic_apiuser')->getUsers();
+                               if (!key_exists($this->APIBasicLogin->Text, $users)) {
+                                       $result = $this->getModule('basic_apiuser')->setUsersConfig(
+                                               $this->APIBasicLogin->Text,
+                                               $this->APIBasicPassword->Text
+                                       );
+                               } else {
+                                       $exists = true;
+                               }
+                       } elseif ($this->Mode === self::MODE_TYPE_EDIT) {
                                $result = $this->getModule('basic_apiuser')->setUsersConfig(
-                                       $this->APIBasicLogin->Text,
+                                       $this->APIBasicLoginHidden->Value,
                                        $this->APIBasicPassword->Text
                                );
-                       } else {
-                               $exists = true;
                        }
-               } elseif ($this->getAuthType() === 'oauth2') {
+               } elseif ($this->getAuthType() === self::AUTH_TYPE_OAUTH2) {
                        $oauth2_cfg = $this->getModule('oauth2_config')->getConfig();
-                       if (!key_exists($this->APIOAuth2ClientId->Text, $oauth2_cfg)) {
-                               $oauth2_cfg[$this->APIOAuth2ClientId->Text] = array();
-                               $oauth2_cfg[$this->APIOAuth2ClientId->Text]['client_id'] = $this->APIOAuth2ClientId->Text;
-                               $oauth2_cfg[$this->APIOAuth2ClientId->Text]['client_secret'] = $this->APIOAuth2ClientSecret->Text;
-                               $oauth2_cfg[$this->APIOAuth2ClientId->Text]['redirect_uri'] = $this->APIOAuth2RedirectURI->Text;
-                               $oauth2_cfg[$this->APIOAuth2ClientId->Text]['scope'] = $this->APIOAuth2Scope->Text;
-                               $oauth2_cfg[$this->APIOAuth2ClientId->Text]['bconsole_cfg_path'] = $this->APIOAuth2BconsoleCfgPath->Text;
-                               $oauth2_cfg[$this->APIOAuth2ClientId->Text]['name'] = $this->APIOAuth2Name->Text;
+                       if ($this->Mode == self::MODE_TYPE_ADD) {
+                               if (!key_exists($this->APIOAuth2ClientId->Text, $oauth2_cfg)) {
+                                       $oauth2_cfg[$this->APIOAuth2ClientId->Text] = [
+                                               'client_id' => $this->APIOAuth2ClientId->Text,
+                                               'client_secret' => $this->APIOAuth2ClientSecret->Text,
+                                               'redirect_uri' => $this->APIOAuth2RedirectURI->Text,
+                                               'scope' => $this->APIOAuth2Scope->Text,
+                                               'bconsole_cfg_path' => $this->APIOAuth2BconsoleCfgPath->Text,
+                                               'name' => $this->APIOAuth2Name->Text
+                                       ];
+                                       $result = $this->getModule('oauth2_config')->setConfig($oauth2_cfg);
+                               } else {
+                                       $exists = true;
+                               }
+                       } elseif ($this->Mode == self::MODE_TYPE_EDIT) {
+                               $oauth2_cfg[$this->APIOAuth2ClientIdHidden->Value] = [
+                                       'client_id' => $this->APIOAuth2ClientIdHidden->Value,
+                                       'client_secret' => $this->APIOAuth2ClientSecret->Text,
+                                       'redirect_uri' => $this->APIOAuth2RedirectURI->Text,
+                                       'scope' => $this->APIOAuth2Scope->Text,
+                                       'bconsole_cfg_path' => $this->APIOAuth2BconsoleCfgPath->Text,
+                                       'name' => $this->APIOAuth2Name->Text
+                               ];
                                $result = $this->getModule('oauth2_config')->setConfig($oauth2_cfg);
-                       } else {
-                               $exists = true;
                        }
                }
 
-               if ($exists === true) {
-                       $this->NewAuthClientAddExists->Display = 'Dynamic';
-               } elseif ($result === true) {
-                       $this->NewAuthClientAddOk->Display = 'Dynamic';
-               } else {
-                       $this->NewAuthClientAddError->Display = 'Dynamic';
+               $cb = true;
+               if ($exists) {
+                       $this->NewAuthClientExists->Display = 'Dynamic';
+                       $cb = false;
+               } elseif ($result !== true) {
+                       $this->NewAuthClientError->Display = 'Dynamic';
+                       $cb = false;
                }
-               $this->onCallback($param);
+               if ($cb) {
+                       $this->onSuccess($param);
+                       $this->clearForm();
+               }
+       }
+
+       public function cancelNewAuthClient($sender, $param) {
+               $this->onCancel($param);
+       }
+
+       public function clearForm() {
+               $this->APIBasicLogin->Text = '';
+               $this->APIBasicPassword->Text = '';
+               $this->RetypeAPIBasicPassword->Text = '';
+               $this->APIOAuth2ClientId->Text = '';
+               $this->APIOAuth2ClientSecret->Text = '';
+               $this->APIOAuth2RedirectURI->Text = '';
+               $this->APIOAuth2Scope->Text = '';
+               $this->APIOAuth2BconsoleCfgPath->Text = '';
+               $this->APIOAuth2Name->Text = '';
        }
 
        public function setShowButtons($show) {
@@ -105,8 +158,23 @@ class NewAuthClient extends PortletTemplate {
                return $this->auth_type;
        }
 
-       public function onCallback($param) {
-               $this->raiseEvent('OnCallback', $this, $param);
+       public function setMode($mode) {
+               if (in_array($mode, $this->modes)) {
+                       $this->setViewState(self::MODE, $mode);
+               }
+       }
+
+       public function getMode() {
+               return $this->getViewState(self::MODE, $this->modes[0]);
+       }
+
+       public function onSuccess($param) {
+               $this->raiseEvent('OnSuccess', $this, $param);
+       }
+
+       public function onCancel($param) {
+               $this->clearForm();
+               $this->raiseEvent('OnCancel', $this, $param);
        }
 
        public function bubbleEvent($sender, $param) {
index ef20eb4da50c201a2dc23cff765c302aa16f6d2c..f9e4bd13cbf2e3ce38d1ae4bd7f4d09153b06b13 100644 (file)
@@ -1,37 +1,47 @@
-<div class="center">
-       <com:TActiveLabel ID="NewAuthClientAddOk" Display="None" CssClass="validate" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="Validate" /> <strong><%[ User added successfully. ]%></strong></com:TActiveLabel>
-       <com:TActiveLabel ID="NewAuthClientAddError" Display="None" CssClass="validator" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="" /> <strong><%[ Problem during save to config file. Please check users config file permission. ]%></strong></com:TActiveLabel>
-       <com:TActiveLabel ID="NewAuthClientAddExists" Display="None" CssClass="validator" EnableViewState="false"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="" /> <strong><%[ Given user already exists in config file. ]%></strong></com:TActiveLabel>
+<div class="w3-center">
+       <com:TActiveLabel ID="NewAuthClientError" Display="None" CssClass="w3-text-red" EnableViewState="false"><i class="fas fa-exclamation-circle"></i> <strong><%[ Problem during save to config file. Please check users config file permission. ]%></strong></com:TActiveLabel>
+       <com:TActiveLabel ID="NewAuthClientExists" Display="None" CssClass="w3-text-red" EnableViewState="false"><i class="fas fa-exclamation-circle"></i> <strong><%[ Given user already exists in config file. ]%></strong></com:TActiveLabel>
 </div>
 <com:TPanel ID="AuthPanel" DefaultButton="NewAuthClient">
-<div id="<%=$this->ClientID%>new_auth_client" style="display: none">
-       <div style="display: <%=($this->getAuthType() == 'basic' ? '' : 'none')%>">
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="APIBasicLogin" Text="<%[ API Login: ]%>" /></div>
-                       <div class="field">
+<div class="w3-container">
+       <div class="w3-padding" style="display: <%=($this->getAuthType() == 'basic' ? '' : 'none')%>">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="APIBasicLogin" Text="<%[ API Login: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
                                <com:TActiveTextBox
                                        ID="APIBasicLogin"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
                                        CausesValidation="false"
+                                       ReadOnly="<%=$this->Mode == 'edit'%>"
+                                       Style="width: 70%"
+                               />
+                               <com:TActiveHiddenField
+                                       ID="APIBasicLoginHidden"
                                />
                                <com:TRequiredFieldValidator
-                                       CssClass="validator-block"
                                        Display="Dynamic"
-                                       ControlCssClass="invalidate"
                                        ControlToValidate="APIBasicLogin"
                                        ValidationGroup="<%=$this->ClientID%>Basic"
                                        Text="<%[ Please enter API login. ]%>"
                                 />
+                               <com:TRegularExpressionValidator
+                                       ValidationGroup="<%=$this->ClientID%>Basic"
+                                       ControlToValidate="APIBasicLogin"
+                                       RegularExpression="<%=BasicUserConfig::USER_PATTERN%>"
+                                       ErrorMessage="<%[ Invalid user. User may contain a-z A-Z 0-9 characters. ]%>"
+                                       Display="Dynamic"
+                               />
                        </div>
                </div>
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="APIBasicPassword" Text="<%[ API Password: ]%>" /></div>
-                       <div class="field">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="APIBasicPassword" Text="<%[ API Password: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
                                <com:TActiveTextBox
                                        ID="APIBasicPassword"
                                        TextMode="Password"
                                        MaxLength="60"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
                                        CausesValidation="false"
                                        PersistPassword="true"
                                />
                                />
                        </div>
                </div>
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="RetypeAPIBasicPassword" Text="<%[ Retype password: ]%>" /></div>
-                       <div class="field">
-                               <com:TTextBox
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-quarter"><com:TLabel ForControl="RetypeAPIBasicPassword" Text="<%[ Retype password: ]%>" /></div>
+                       <div class="w3-col w3-threequarter">
+                               <com:TActiveTextBox
                                        ID="RetypeAPIBasicPassword"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
                                        TextMode="Password"
                                        MaxLength="60"
                                        PersistPassword="true"
                </div>
        </div>
        <div style="display: <%=($this->getAuthType() == 'oauth2' ? '' : 'none')%>">
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="APIOAuth2ClientId" Text="<%[ OAuth2 Client ID: ]%>" /></div>
-                       <div class="field">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2ClientId" Text="<%[ OAuth2 Client ID: ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TTextBox
                                        ID="APIOAuth2ClientId"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
                                        CausesValidation="false"
+                                       ReadOnly="<%=$this->Mode == 'edit'%>"
                                        MaxLength="32"
                                />
+                               <com:TActiveHiddenField
+                                       ID="APIOAuth2ClientIdHidden"
+                               />
                                <com:TRequiredFieldValidator
                                        CssClass="validator-block"
                                        Display="Dynamic"
                                        ValidationGroup="<%=$this->ClientID%>OAuth2"
                                        Text="<%[ Invalid Client ID value. Client ID may contain a-z A-Z 0-9 - _ characters. ]%>"
                                        />
-                               <a href="javascript:void(0)" onclick="document.getElementById('<%=$this->APIOAuth2ClientId->ClientID%>').value = get_random_string('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 32); return false;"><%[ generate ]%></a>
+                               <a href="javascript:void(0)" onclick="document.getElementById('<%=$this->APIOAuth2ClientId->ClientID%>').value = get_random_string('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 32); return false;" style="display: <%=$this->Mode == 'edit' ? 'none': 'inline'%>"><%[ generate ]%></a>
                        </div>
                </div>
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="APIOAuth2ClientSecret" Text="<%[ OAuth2 Client Secret: ]%>" /></div>
-                       <div class="field">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2ClientSecret" Text="<%[ OAuth2 Client Secret: ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TTextBox
                                        ID="APIOAuth2ClientSecret"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
                                        CausesValidation="false"
                                        MaxLength="50"
                                />
                                <a href="javascript:void(0)" onclick="document.getElementById('<%=$this->APIOAuth2ClientSecret->ClientID%>').value = get_random_string('ABCDEFabcdef0123456789', 40); return false;"><%[ generate ]%></a>
                        </div>
                </div>
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="APIOAuth2RedirectURI" Text="<%[ OAuth2 Redirect URI (example: https://baculumgui:9095/web/redirect): ]%>" /></div>
-                       <div class="field">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2RedirectURI" Text="<%[ OAuth2 Redirect URI (example: https://baculumgui:9095/web/redirect): ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TTextBox
                                        ID="APIOAuth2RedirectURI"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
                                        CausesValidation="false"
                                />
                                <com:TRequiredFieldValidator
                                />
                        </div>
                </div>
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="APIOAuth2Scope" Text="<%[ OAuth2 scopes (space separated): ]%>" /></div>
-                       <div class="field">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2Scope" Text="<%[ OAuth2 scopes (space separated): ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TTextBox
                                        ID="APIOAuth2Scope"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
                                        CausesValidation="false"
                                        TextMode="MultiLine"
                                />
                                />
                        </div>
                </div>
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="APIOAuth2BconsoleCfgPath" Text="<%[ Dedicated Bconsole config file path: ]%>" /></div>
-                       <div class="field">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2BconsoleCfgPath" Text="<%[ Dedicated Bconsole config file path: ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TTextBox
                                        ID="APIOAuth2BconsoleCfgPath"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
                                        CausesValidation="false"
                                /> <%[ (optional) ]%>
                        </div>
                </div>
-               <div class="line">
-                       <div class="text"><com:TLabel ForControl="APIOAuth2Name" Text="<%[ Short name: ]%>" /></div>
-                       <div class="field">
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIOAuth2Name" Text="<%[ Short name: ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TTextBox
                                        ID="APIOAuth2Name"
-                                       CssClass="textbox"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
                                        CausesValidation="false"
                                /> <%[ (optional) ]%>
                        </div>
                </div>
        </div>
-       <div class="center" style="width: 550px;<%=($this->getShowButtons() ? '' : 'display: none;')%>">
-               <com:BButton
-                       Text="<%[ Cancel ]%>"
-                       CausesValidation="false"
-                       Attributes.onclick="$('#<%=$this->ClientID%>new_auth_client').slideUp(); return false;"
-               />
-               <com:BActiveButton
+       <div class="w3-center w3-section" style="<%=($this->getShowButtons() ? '' : 'display: none;')%>">
+               <com:TActiveLinkButton
+                       CssClass="w3-button w3-red"
+                       OnCallback="TemplateControl.cancelNewAuthClient"
+                       >
+                       <i class="fas fa-times"></i> &nbsp;<%[ Cancel ]%>
+               </com:TActiveLinkButton>
+               <com:TActiveLinkButton
                        ID="NewAuthClient"
                        ValidationGroup="<%=$this->ClientID%>NewAuthClientGroup"
-                       OnCommand="TemplateControl.addNewAuthClient"
-                       Text="<%[ Add ]%>"
-                       Attributes.onclick="return <%=$this->ClientID%>fields_validation()"
+                       OnCommand="TemplateControl.saveNewAuthClient"
+                       Attributes.onclick="return <%=$this->ClientID%>oNewAuthClient.fields_validation()"
+                       CssClass="w3-button w3-green"
                >
                        <prop:ClientSide.OnPreDispatch>
-                               $('#<%=$this->NewAuthClientAddOk->ClientID%>').hide();
-                               $('#<%=$this->NewAuthClientAddError->ClientID%>').hide();
-                               $('#<%=$this->NewAuthClientAddExists->ClientID%>').hide();
+                               $('#<%=$this->NewAuthClientError->ClientID%>').hide();
+                               $('#<%=$this->NewAuthClientExists->ClientID%>').hide();
                        </prop:ClientSide.OnPreDispatch>
                        <prop:ClientSide.OnComplete>
-                               var msg_ok = $('#<%=$this->NewAuthClientAddOk->ClientID%>'); 
-                               if (msg_ok.is(':visible')) {
-                                       $('#<%=$this->ClientID%>new_auth_client').slideUp();
-                                       setTimeout(function() {
-                                               $('#<%=$this->NewAuthClientAddOk->ClientID%>').slideUp();
-                                       }, 5000);
-                               }
+                               $('#<%=$this->ClientID%>new_auth_client').hide();
                        </prop:ClientSide.OnComplete>
-               </com:BActiveButton>
+                       <i class="fas fa-save"></i> &nbsp;<%[ Save ]%>
+               </com:TActiveLinkButton>
        </div>
 </div>
 </com:TPanel>
 <script type="text/javascript">
-       var <%=$this->ClientID%>fields_validation = function() {
-               var basic = <%=($this->getAuthType() === 'basic' ? 1 : 0)%>;
-               var oauth2 = <%=($this->getAuthType() === 'oauth2' ? 1 : 0)%>;
+var <%=$this->ClientID%>oNewAuthClient = {
+       mode: '<%=$this->Mode%>',
+       ids: {
+               basic: {
+                       username: '<%=$this->APIBasicLogin->ClientID%>',
+                       username_hidden: '<%=$this->APIBasicLoginHidden->ClientID%>',
+                       password: '<%=$this->APIBasicPassword->ClientID%>',
+                       password_retype: '<%=$this->RetypeAPIBasicPassword->ClientID%>'
+               },
+               oauth2: {
+                       client_id: '<%=$this->APIOAuth2ClientId->ClientID%>',
+                       client_id_hidden: '<%=$this->APIOAuth2ClientIdHidden->ClientID%>',
+                       client_secret: '<%=$this->APIOAuth2ClientSecret->ClientID%>',
+                       redirect_uri: '<%=$this->APIOAuth2RedirectURI->ClientID%>',
+                       scope: '<%=$this->APIOAuth2Scope->ClientID%>',
+                       bconsole_cfg_path: '<%=$this->APIOAuth2BconsoleCfgPath->ClientID%>',
+                       name: '<%=$this->APIOAuth2Name->ClientID%>'
+               },
+               errors: {
+                       generic: '<%=$this->NewAuthClientError->ClientID%>',
+                       exists: '<%=$this->NewAuthClientExists->ClientID%>'
+               }
+       },
+       set_basic_props: function(props) {
+               if (!props || typeof(props) != 'object') {
+                       return false;
+               }
+               if (props.hasOwnProperty('username')) {
+                       document.getElementById(this.ids.basic.username).value = props.username;
+                       document.getElementById(this.ids.basic.username_hidden).value = props.username;
+               }
+       },
+       set_oauth2_props: function(props) {
+               if (!props || typeof(props) != 'object') {
+                       return false;
+               }
+               if (props.hasOwnProperty('client_id')) {
+                       document.getElementById(this.ids.oauth2.client_id).value = props.client_id;
+                       document.getElementById(this.ids.oauth2.client_id_hidden).value = props.client_id;
+               }
+               if (props.hasOwnProperty('client_secret')) {
+                       document.getElementById(this.ids.oauth2.client_secret).value = props.client_secret;
+               }
+               if (props.hasOwnProperty('redirect_uri')) {
+                       document.getElementById(this.ids.oauth2.redirect_uri).value = props.redirect_uri;
+               }
+               if (props.hasOwnProperty('scope')) {
+                       document.getElementById(this.ids.oauth2.scope).value = props.scope;
+               }
+               if (props.hasOwnProperty('bconsole_cfg_path')) {
+                       document.getElementById(this.ids.oauth2.bconsole_cfg_path).value = props.bconsole_cfg_path;
+               }
+               if (props.hasOwnProperty('name')) {
+                       document.getElementById(this.ids.oauth2.name).value = props.name;
+               }
+       },
+       clear_basic_fields: function() {
+               document.getElementById(this.ids.basic.username).value = '';
+               document.getElementById(this.ids.basic.username_hidden).value = '';
+               document.getElementById(this.ids.basic.password).value = '';
+               document.getElementById(this.ids.basic.password_retype).value = '';
+       },
+       clear_oauth2_fields: function() {
+               document.getElementById(this.ids.oauth2.client_id).value = '';
+               document.getElementById(this.ids.oauth2.client_id_hidden).value = '';
+               document.getElementById(this.ids.oauth2.client_secret).value = '';
+               document.getElementById(this.ids.oauth2.redirect_uri).value = '';
+               document.getElementById(this.ids.oauth2.scope).value = '';
+               document.getElementById(this.ids.oauth2.bconsole_cfg_path).value = '';
+               document.getElementById(this.ids.oauth2.name).value = '';
+       },
+       hide_errors: function() {
+               document.getElementById(this.ids.errors.generic).style.display = 'none';
+               document.getElementById(this.ids.errors.exists).style.display = 'none';
+       },
+       fields_validation: function() {
+               var basic = <%=($this->getAuthType() === NewAuthClient::AUTH_TYPE_BASIC ? 1 : 0)%>;
+               var oauth2 = <%=($this->getAuthType() === NewAuthClient::AUTH_TYPE_OAUTH2 ? 1 : 0)%>;
                var validation_group;
                if (basic) {
                        validation_group = '<%=$this->ClientID%>Basic';
                }
                return Prado.Validation.validate(Prado.Validation.getForm(), validation_group);
        }
+};
 </script>
index 0f83be61ebd69f0eca295142b610fa3f06e1ed9d..b81f7943b433c0de59b33f2c3b44cadf9ce064dd 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
@@ -47,7 +47,7 @@ class BaculumAPIClient extends WebModule {
        /**
         * API version used by Web
         */
-       const API_VERSION = 'v1';
+       const API_VERSION = 'v2';
 
        /**
         * OAuth2 authorization endpoints
@@ -337,7 +337,7 @@ class BaculumAPIClient extends WebModule {
                $host_cfg = $this->getHostParams($host);
                $uri = $this->getURIResource($host, $params);
                $ch = $this->getConnection($host_cfg);
-               $data = http_build_query(array('update' => $options));
+               $data = json_encode($options);
                curl_setopt($ch, CURLOPT_URL, $uri);
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
                curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge(
@@ -374,7 +374,7 @@ class BaculumAPIClient extends WebModule {
                $host_cfg = $this->getHostParams($host);
                $uri = $this->getURIResource($host, $params);
                $ch = $this->getConnection($host_cfg);
-               $data = http_build_query(array('create' => $options));
+               $data = json_encode($options);
                curl_setopt($ch, CURLOPT_URL, $uri);
                curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($this->getAPIHeaders($host, $host_cfg), array('Expect:')));
                curl_setopt($ch, CURLOPT_POST, true);
index f6a385c1ca217a44ce95c8a1096407340d2c5270..1cb04d4b3e518e4f440ba3f2cac2dd820fec8cc6 100644 (file)
@@ -391,33 +391,6 @@ function set_formatters() {
        Formatters.set_formatters();
 }
 
-var Cookies = {
-       default_exipration_time: 31536000000, // 1 year in miliseconds
-       set_cookie: function(name, value, expiration) {
-               var date = new Date();
-               date.setTime(date.getTime() + this.default_exipration_time);
-               var expires = 'expires=' + date.toUTCString();
-               document.cookie = name + '=' + value + '; ' + expires;
-       },
-       get_cookie: function(name) {
-               name += '=';
-               var values = document.cookie.split(';');
-               var cookie_val = null;
-               var value;
-               for (var i = 0; i < values.length; i++) {
-                       value = values[i];
-                       while (value.charAt(0) == ' ') {
-                               value = value.substr(1);
-                       }
-                       if (value.indexOf(name) == 0) {
-                               cookie_val = value.substring(name.length, value.length);
-                               break;
-                       }
-               }
-               return cookie_val;
-       }
-}
-
 var JobStatus = {
        st: {
                ok: ['T', 'D'],
@@ -936,135 +909,6 @@ var Weather = {
        }
 };
 
-var W3SideBar = {
-       ids: {
-               sidebar: 'sidebar',
-               overlay_bg: 'overlay_bg'
-       },
-       css: {
-               page_main: '.page_main_el'
-       },
-       cookies: {
-               side_bar_hide: 'baculum_side_bar_hide'
-       },
-       init: function() {
-               this.sidebar = document.getElementById(this.ids.sidebar);
-               if (!this.sidebar) {
-                       // don't initialize for pages without sidebar
-                       return;
-               }
-               this.overlay_bg = document.getElementById(this.ids.overlay_bg);
-               this.page_main = $(this.css.page_main);
-               var hide = Cookies.get_cookie(this.cookies.side_bar_hide);
-               if (hide == 1) {
-                       this.close();
-               }
-               if (!this.sidebar) {
-                       // on pages without sidebar always show page main elements with 100% width.
-                       this.page_main.css({'margin-left': '0', 'width': '100%'});
-               }
-               this.set_events();
-       },
-       set_events: function() {
-               if (this.sidebar) {
-                       this.sidebar.addEventListener('touchstart', handle_touch_start);
-                       this.sidebar.addEventListener('touchmove', function(e) {
-                               handle_touch_move(e, {
-                                       'swipe_left': function() {
-                                               this.close();
-                                       }.bind(this)
-                               });
-                       }.bind(this));
-               }
-       },
-       open: function() {
-               if (this.sidebar.style.display === 'block' || this.sidebar.style.display === '') {
-                       this.close();
-               } else {
-                       Cookies.set_cookie('baculum_side_bar_hide', 0);
-                       this.sidebar.style.display = 'block';
-                       this.overlay_bg.style.display = 'block';
-                       this.page_main.css({'margin-left': '250px', 'width': 'calc(100% - 250px)'});
-               }
-       },
-       close: function() {
-               Cookies.set_cookie('baculum_side_bar_hide', 1);
-               this.sidebar.style.display = 'none';
-               this.overlay_bg.style.display = 'none';
-               this.page_main.css({'margin-left': '0', 'width': '100%'});
-       }
-};
-
-W3TabsCommon = {
-       open: function(btn_id, item_id) {
-               var tab_items = document.getElementsByClassName(this.css.tab_item);
-               for (var i = 0; i < tab_items.length; i++) {
-                       if (tab_items[i].id === item_id) {
-                               tab_items[i].style.display = 'block';
-                       } else {
-                               tab_items[i].style.display = 'none';
-                       }
-               }
-               var tab_btns = document.getElementsByClassName(this.css.tab_btn);
-               for (var i = 0; i < tab_btns.length; i++) {
-                       if (tab_btns[i].id === btn_id && !tab_btns[i].classList.contains(this.css.tab_item_hover)) {
-                               tab_btns[i].classList.add(this.css.tab_item_hover);
-                       } else if (tab_btns[i].id !== btn_id && tab_btns[i].classList.contains(this.css.tab_item_hover)) {
-                               tab_btns[i].classList.remove(this.css.tab_item_hover);
-                       }
-               }
-       },
-       is_open: function(item_id) {
-               var display = document.getElementById(item_id).style.display;
-               return (display === 'block' || display === '');
-       }
-};
-
-W3Tabs = {
-       css: {
-               tab_btn: 'tab_btn',
-               tab_item: 'tab_item',
-               tab_item_hover: 'w3-grey'
-       },
-       open: function(btn_id, item_id) {
-               W3TabsCommon.open.call(this, btn_id, item_id);
-       },
-       is_open: function(item_id) {
-               return W3TabsCommon.is_open(item_id);
-       }
-};
-
-W3SubTabs = {
-       css: {
-               tab_btn: 'subtab_btn',
-               tab_item: 'subtab_item',
-               tab_item_hover: 'w3-border-red'
-       },
-       open: function(btn_id, item_id) {
-               W3TabsCommon.open.call(this, btn_id, item_id);
-       }
-};
-var OAuth2Scopes = [
-       'console',
-       'jobs',
-       'directors',
-       'clients',
-       'storages',
-       'volumes',
-       'pools',
-       'bvfs',
-       'joblog',
-       'filesets',
-       'schedules',
-       'config',
-       'status',
-       'actions',
-       'oauth2'
-];
-var set_scopes = function(field_id) {
-       document.getElementById(field_id).value = OAuth2Scopes.join(' ');
-}
-
 function estimate_job(jobs, job, level) {
        var bytes = 0;
        var files = 0;
@@ -1151,15 +995,6 @@ function openElementOnCursor(e, element, offsetX, offsetY) {
        $('#' + element).show();
 }
 
-
-function get_random_string(allowed, len) {
-       var random_string = "";
-       for(var i = 0; i < len; i++) {
-               random_string += allowed.charAt(Math.floor(Math.random() * allowed.length));
-       }
-       return random_string;
-}
-
 function clear_node(selector) {
        var node = $(selector);
        for (var i = 0; i < node.length; i++) {
@@ -1204,51 +1039,6 @@ function set_icon_css() {
        $('.w3-spin').removeClass('w3-spin').addClass('fa-spin');
 }
 
-var touch_start_x = null;
-var touch_start_y = null;
-
-function handle_touch_start(e) {
-       // browser API or jQuery
-       var first_touch =  e.touches || e.originalEvent.touches
-       touch_start_x = first_touch[0].clientX;
-       touch_start_y = first_touch[0].clientY;
-}
-
-function handle_touch_move(e, callbacks) {
-       if (!touch_start_x || !touch_start_y || typeof(callbacks) !== 'object') {
-               // no touch type event or no callbacks
-               return;
-       }
-
-       var touch_end_x = e.touches[0].clientX;
-       var touch_end_y = e.touches[0].clientY;
-
-       var touch_diff_x = touch_start_x - touch_end_x;
-       var touch_diff_y = touch_start_y - touch_end_y;
-
-       if (Math.abs(touch_diff_x) > Math.abs(touch_diff_y)) {
-               if (touch_diff_x > 0 && callbacks.hasOwnProperty('swipe_left')) {
-                       // left swipe
-                       callbacks.swipe_left();
-               } else if (callbacks.hasOwnProperty('swipe_right')) {
-                       // right swipe
-                       callbacks.swipe_right();
-               }
-       } else {
-               if (touch_diff_y > 0 && callbacks.hasOwnProperty('swipe_up')) {
-                       // up swipe
-                       callbacks.swipe_up()
-               } else if (callbacks.hasOwnProperty('swipe_down')) {
-                       // down swipe
-                       callbacks.swipe_down()
-               }
-       }
-
-       // reset values
-       touch_start_x = null;
-       touch_start_y = null;
-}
-
 function sort_natural(a, b) {
        a = a.toString();
        b = b.toString();
@@ -1404,21 +1194,7 @@ function get_table_toolbar(table, actions, txt) {
        return table_toolbar;
 }
 
-function set_global_listeners() {
-       document.addEventListener('keydown', function(e) {
-               var key_code = e.keyCode || e.which;
-               switch (key_code) {
-                       case 27: { // escape
-                               $('.w3-modal').filter(':visible').hide(); // hide modals
-                               break;
-                       }
-               }
-       });
-}
-
 $(function() {
-       W3SideBar.init();
        set_sbbr_compatibility();
        set_icon_css();
-       set_global_listeners();
 });
index 16e29485bb941b773358a20cf66baa63712070a0..e93aa840fecb3985ac0cbf9ed9c304a509785018 100644 (file)
@@ -8,15 +8,16 @@
        <body  class="w3-light-grey">
                <com:TForm>
                        <com:TClientScript PradoScripts="ajax, effects" />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/misc.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/fontawesome.min.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/datatables.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/dataTables.responsive.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/responsive.jqueryui.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/dataTables.buttons.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/buttons.html5.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/buttons.colVis.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/dataTables.select.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/flotr2.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/fontawesome.min.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/datatables.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.responsive.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/responsive.jqueryui.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.buttons.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.html5.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.colVis.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.select.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/bacula-config.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/misc.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/graph.js %> />
index 70a8a1907b3774694eebf1864bff29a6bceca193..1a638d364df71ac352fb15bc09a7919c54a10532 100644 (file)
@@ -3,7 +3,7 @@
        <com:THead Title="Baculum - Bacula Web Interface" ShortcutIcon="<%=$this->getPage()->getTheme()->getBaseUrl()%>/favicon.ico" />
        <body id="message-body">
                <com:TForm>
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/fontawesome.min.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/fontawesome.min.js %> />
                        <com:TContentPlaceHolder ID="Message" />
                </com:TForm>
        </body>
index 6ad414633290f36a01d16e2897742965b76b218c..7ebec7692f7511c882ed7043efd96f1f4486689a 100644 (file)
@@ -7,7 +7,7 @@
        </com:THead>
        <body  class="w3-light-grey">
                <com:TForm>
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/fontawesome.min.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/fontawesome.min.js %> />
                        <!-- Top container -->
                        <div class="w3-bar w3-top w3-black w3-large" style="z-index:4">
                                <span class="w3-bar-item w3-right">
index a06ec91545294cd07fdf0f82405fd9a6ce5a0a6b..467daa8ae39bdeb7603f4ac5616794722afa077b 100644 (file)
                </script>
                <com:TForm>
                        <com:TClientScript PradoScripts="ajax, effects" />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/fontawesome.min.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/datatables.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.responsive.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/responsive.jqueryui.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.buttons.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.html5.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.colVis.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/misc.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/fontawesome.min.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/datatables.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/dataTables.responsive.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/responsive.jqueryui.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/dataTables.buttons.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/buttons.html5.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../../Common/JavaScript/buttons.colVis.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/opentip.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/tooltip.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/misc.js %> />
index bbd61990a8de8d29d17f2123941da096ebbd6a52..a7b4ac161ff9a70f8d705d17d8caba80baf87ab3 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
@@ -129,13 +129,13 @@ class ClientView extends BaculumWebPage {
 
        public function status($sender, $param) {
                $raw_status = $this->getModule('api')->get(
-                       array('clients', $this->getClientId(), 'status')
+                       ['clients', $this->getClientId(), 'status']
                )->output;
                $this->ClientLog->Text = implode(PHP_EOL, $raw_status);
 
-               $query_str = '?name=' . rawurlencode($this->getClientName()) . '&type=header';
+               $query_str = '?output=json&type=header';
                $graph_status = $this->getModule('api')->get(
-                       array('status', 'client', $query_str)
+                       ['clients', $this->getClientId(), 'status', $query_str]
                );
                $client_status = array(
                        'header' => array(),
@@ -153,9 +153,9 @@ class ClientView extends BaculumWebPage {
                        }
                }
 
-               $query_str = '?name=' . rawurlencode($this->getClientName()) . '&type=running';
+               $query_str = '?output=json&type=running';
                $graph_status = $this->getModule('api')->get(
-                       array('status', 'client', $query_str)
+                       ['clients', $this->getClientId(), 'status', $query_str]
                );
                if ($graph_status->error === 0) {
                        $client_status['running'] = $graph_status->output;
index e693b801a1b0a722b6c7cf81e70e4eb39a454c51..084eed46fa3f33f3e06c133b76496f5953867cce 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
@@ -203,9 +203,9 @@ class StorageView extends BaculumWebPage {
                )->output;
                $this->StorageLog->Text = implode(PHP_EOL, $raw_status);
 
-               $query_str = '?name=' . rawurlencode($this->getStorageName()) . '&type=header';
+               $query_str = '?output=json&type=header';
                $graph_status = $this->getModule('api')->get(
-                       ['status', 'storage', $query_str]
+                       ['storages', $this->getStorageId(), 'status', $query_str]
                );
                $storage_status = [
                        'header' => [],
@@ -219,27 +219,27 @@ class StorageView extends BaculumWebPage {
                }
 
                // running
-               $query_str = '?name=' . rawurlencode($this->getStorageName()) . '&type=running';
+               $query_str = '?output=json&type=running';
                $graph_status = $this->getModule('api')->get(
-                       array('status', 'storage', $query_str)
+                       ['storages', $this->getStorageId(), 'status', $query_str]
                );
                if ($graph_status->error === 0) {
                        $storage_status['running'] = $graph_status->output;
                }
 
                // terminated
-               $query_str = '?name=' . rawurlencode($this->getStorageName()) . '&type=terminated';
+               $query_str = '?output=json&type=terminated';
                $graph_status = $this->getModule('api')->get(
-                       array('status', 'storage', $query_str)
+                       ['storages', $this->getStorageId(), 'status', $query_str]
                );
                if ($graph_status->error === 0) {
                        $storage_status['terminated'] = $graph_status->output;
                }
 
                // devices
-               $query_str = '?name=' . rawurlencode($this->getStorageName()) . '&type=devices';
+               $query_str = '?output=json&type=devices';
                $graph_status = $this->getModule('api')->get(
-                       array('status', 'storage', $query_str)
+                       ['storages', $this->getStorageId(), 'status', $query_str]
                );
                if ($graph_status->error === 0) {
                        $storage_status['devices'] = $graph_status->output;
diff --git a/gui/baculum/themes/Baculum-v1/ajax-loader.gif b/gui/baculum/themes/Baculum-v1/ajax-loader.gif
deleted file mode 100644 (file)
index 2fd8e07..0000000
Binary files a/gui/baculum/themes/Baculum-v1/ajax-loader.gif and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/ajax-loader.orig.gif b/gui/baculum/themes/Baculum-v1/ajax-loader.orig.gif
deleted file mode 100644 (file)
index 61447df..0000000
Binary files a/gui/baculum/themes/Baculum-v1/ajax-loader.orig.gif and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/background.png b/gui/baculum/themes/Baculum-v1/background.png
deleted file mode 100644 (file)
index bcde424..0000000
Binary files a/gui/baculum/themes/Baculum-v1/background.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/bls_bottom.png b/gui/baculum/themes/Baculum-v1/bls_bottom.png
deleted file mode 100644 (file)
index f35a759..0000000
Binary files a/gui/baculum/themes/Baculum-v1/bls_bottom.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/bls_top.png b/gui/baculum/themes/Baculum-v1/bls_top.png
deleted file mode 100644 (file)
index 1d21f86..0000000
Binary files a/gui/baculum/themes/Baculum-v1/bls_top.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/close.png b/gui/baculum/themes/Baculum-v1/close.png
deleted file mode 100644 (file)
index 5735484..0000000
Binary files a/gui/baculum/themes/Baculum-v1/close.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/css/style.css b/gui/baculum/themes/Baculum-v1/css/style.css
deleted file mode 100644 (file)
index ed9891c..0000000
+++ /dev/null
@@ -1,370 +0,0 @@
-body {
-       background-color: #b5c9d3;
-       background-image: url('../background.png');
-       background-repeat: repeat-x;
-       font-family: Arial, Helvetica, sans-serif;
-       color: white;
-}
-
-body.api {
-       background-image: none;
-       background-color: #444445;
-       color: white;
-}
-
-a {
-       font-size: 12px;
-       color: white;
-       padding: 3px 4px;
-       text-decoration: none;
-}
-
-a:hover {
-       text-decoration: underline;
-}
-
-
-input.invalidate, select.invalidate {
-       border: 1px solid red;
-}
-
-input[type=text], input[type=password], select, textarea {
-               -webkit-border-radius: 4px;
-               -moz-border-radius: 4px;
-               border-radius: 4px;
-               padding: 5px;
-}
-
-input[type=checkbox] {
-               height: 16px;
-               width: 16px;
-}
-
-input.textbox, select.textbox, textarea.textbox {
-       width: 300px;
-       border: 1px solid black;
-       font-size: 11pt !important;
-       display: table-cell;
-}
-
-input.textbox-short, select.textbox-short {
-       width: 80px;
-       border: 1px solid black;
-       font-size: 11pt;
-       display: table-cell;
-}
-
-input.textbox-auto, select.textbox-auto, textarea.textbox-auto {
-       border: 1px solid black;
-       font-size: 14px;
-       display: table-cell;
-}
-
-textarea.textbox-auto {
-       resize: vertical;
-       width: 100%;
-       font-size: 11px;
-}
-
-input.bbutton {
-       font-size: 12px !important;
-       vertical-align: baseline;
-       -webkit-appearance: none;
-       text-shadow: 0 1px 1px #7b2121;
-       font-family: "times, serif" !important;
-       font-weight: 600;
-       color: white;
-       height: 28px;
-       line-height: 26px;
-       cursor: pointer;
-       display: inline-block;
-       width: auto;
-       margin: 5px 8px;
-       padding: 0 16px;
-       min-width: 65px;
-       border-radius: 4px;
-       border: 1px solid #8f2626;
-       background: #ca3838;
-       background-image: linear-gradient(#df8989, #d03232);
-}
-
-input.bbutton:hover {
-       padding: 0 16px;
-       box-shadow: inset 0 0 3px #faeded;
-       background: #ff7474;
-       border: 1px solid #8f2626;
-}
-
-.center {
-       text-align: center !important;
-}
-
-.left {
-       text-align: left !important;
-}
-
-.right {
-       text-align: right;
-}
-
-.api_button, .api_select {
-       font-weight: bold;
-       border: 1px solid #212121;
-       border-radius: 4px;
-       height: 37px;
-       display: inline-block;
-       vertical-align: super;
-}
-
-.api_button {
-       color: #1a1b1d;
-       background-color: #ffeb3b;
-}
-
-.api_button:hover {
-       color: black;
-       background-color: white;
-       cursor: pointer;
-       transition: all 0.3s linear;
-}
-
-.validator, .validate, .validator-block, .validator-info {
-       color: #f58589 !important;
-       font-size: 10pt;
-       /*margin-left: 5px;*/
-}
-
-.validator-info {
-       color: white !important;
-}
-
-.validator-block {
-       display: block;
-}
-
-.block {
-       display: block !important;
-}
-
-.validate {
-       color: #96c600 !important;
-}
-
-.line {
-       min-height: 35px;
-       clear: both;
-}
-
-.text {
-       width: 240px;
-       display: table-cell;
-       vertical-align: middle;
-       font-size: 14px;
-}
-
-.field {
-       display: table-cell;
-}
-
-.field-full {
-       display: block;
-       min-height: 200px;
-       width: 98%;
-}
-
-.footer {
-       font-size: 12px;
-       text-align: right;
-       margin-top: 5px;
-}
-
-/* API server OAuth2 progress bar */
-
-#progress_label {
-       position: absolute;
-       top: 114px;
-       transform: translate(-50%, -50%);
-       font-weight: bold;
-}
-
-#api_command {
-       border-radius: 5px;
-       height: 26px;
-       width: 617px;
-       font-size: 25px;
-       color: #444444;
-       padding: 9px 10px 2px 9px;
-}
-
-#api_output {
-       margin-top: 20px;
-       border-radius: 6px;
-       border: 1px solid white;
-       width: 75%;
-}
-
-#api_result {
-       margin: 0 auto;
-       white-space: pre-wrap;
-       word-wrap: break-word;
-       font-size: 13px;
-}
-
-.api_button, .api_select {
-       font-weight: bold;
-       border: 1px solid #212121;
-       border-radius: 4px;
-       height: 37px;
-       display: inline-block;
-       vertical-align: super;
-}
-
-.api_button {
-       color: #1a1b1d;
-       background-color: #ffeb3b;
-}
-
-.api_button:hover {
-       color: black;
-       background-color: white;
-       cursor: pointer;
-       transition: all 0.3s linear;
-}
-
-#api_refresh {
-       margin-left: 10px;
-       cursor: pointer;
-}
-
-/**
- * Override jQuery-UI theming.
- */
-
-.ui-widget-content td a, .ui-widget-content div a {
-       color: white !important;
-       text-decoration: underline !important;
-}
-
-.ui-dialog-content div a {
-       color: black !important;
-       text-decoration: underline !important;
-}
-
-.ui-tabs {
-       font-size: 12px !important;
-}
-
-.ui-tabs .ui-tabs-panel {
-       background-color: #444445 !important;
-       color: white !important;
-       font-size: 15px !important;
-}
-
-.tab-grid {
-       border-spacing: 0 !important;
-}
-
-.tab-grid td  {
-       border-top: 1px solid white !important;
-       padding: 8px !important;
-}
-
-.error {
-       color: red;
-}
-
-fieldset {
-       margin-bottom: 15px;
-       font-size: 14px;
-}
-
-fieldset .line {
-       min-height: initial;
-       height: 25px;
-}
-
-fieldset .text {
-       width: 250px;
-}
-
-legend {
-       font-size: 12px;
-       font-weight: bold;
-       color: white;
-}
-
-img {
-       vertical-align: middle;
-       border: none;
-}
-
-h4 {
-       margin: 6px 0;
-}
-
-.test_label {
-       display: inline-block;
-       height: 34px;
-}
-
-.config_lines {
-       width: 590px;
-       float: left;
-}
-.actions_lines {
-       width: 743px;
-}
-
-.config_lines div.field {
-       width: 280px !important;
-}
-
-.actions_lines div.field {
-       width: 332px !important;
-}
-
-#config_fields, #actions_fields {
-       width: 732px;
-       margin: 0 auto;
-       padding-top: 7px;
-}
-
-#actions_fields {
-       width: 810px;
-}
-
-#actions_fields .button {
-       display: inline-block;
-       min-width: 50px;
-}
-
-#actions_fields .button input[type="submit"] {
-       min-width: 60px;
-}
-
-#actions_fields .action_test_loader, #actions_fields .action_success, #actions_fields .action_error {
-       margin-left: 11px;
-}
-
-#config_fields div.button {
-       padding-left: 247px;
-}
-
-fieldset.config_field {
-       width: 667px;
-}
-
-fieldset.actions_field {
-       width: 745px;
-}
-
-fieldset.config_field .line, fieldset.actions_field .line {
-       height: auto;
-       min-height: 35px !important;
-       padding-top: 2px;
-       padding-bottom: 2px;
-}
-
-span.config_test_loader, span.config_test_result {
-       line-height: 75px;
-}
diff --git a/gui/baculum/themes/Baculum-v1/css/wizard.css b/gui/baculum/themes/Baculum-v1/css/wizard.css
deleted file mode 100644 (file)
index d7d891a..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-.wizard {
-       min-height: 547px;
-       width: 1024px;
-       margin: 50px auto;
-       font-family: Arial;
-       color: white;
-       background-color: #585756;
-       background-image: url('../step-head.png');
-       background-repeat: repeat-x;
-       border: 1px solid black;
-       border-radius: 15px;
-}
-
-.steps {
-       padding: 8px 20px;
-       background-image: none;
-       min-height: 520px;
-}
-
-.steps div.line {
-       text-align: center;
-}
-.steps div.text, .steps div.field {
-       display: inline-block;
-       text-align: left;
-}
-
-.steps div.field {
-       width: 420px;
-}
-
-.navigation {
-       text-align: center;
-       padding: 5px 0;
-       background-image: url('../wizard-bottom-1.png');
-       background-repeat: repeat-x;
-       border-bottom-left-radius: 14px;
-       border-bottom-right-radius: 14px;
- }
-.step {
-       float: left;
-       height: 45px;
-       width: 124px;
-       font-size: 15px;
-       text-align: center;
-       position: relative;
- }
-.step div {
-       position: absolute;
-       top: 50%;
-       left: 45%;
-       transform: translate(-50%, -50%);
-       width: 115px;
- }
-.step-normal,  .step-active,  .step-prev-active, .step-first-active, .step-first-next-active {
-       background-repeat: no-repeat;
-       background-position: right center;
- }
-  .step-first {
-       width: 126px;
-       background-image: url('../step-first.png');
- }
- .step-first-active {
-       width: 126px;
-       background-image: url('../step-first-active.png');
- }
- .step-first-next-active{
-       width: 126px;
-       background-image: url('../step-first-next-active.png');
- }
-
-.step-last {
-       width: 130px;
-       background-image: url('../step-last-1.png');
- }
-
- .step-last-active {
-       width: 130px;
-       background-image: url('../step-last-active-1.png');
- }
- .step-last-next-active{
-       width: 130px;
-       background-image: url('../step-last-next-active.png');
- }
-
-.step-normal, .step-prev-active {
-       background-image: url('../step.png');
- }
-.step-active{
-       background-image: url('../step-active.png');
- }
-.step-prev-active {
-       background-image: url('../step-prev-active.png');
- }
-#title {
-       clear: left;
-       font-size: 14px;
-       font-weight: bold;
-       background-image: url('../wizard-header-1.png');
-       background-repeat: repeat-x;
-       padding: 4px;
-}
-
-div.button-cancel {
-       float: right;
-       margin-right: 28px
-}
-
-div.button-prev-next {
-       margin-left: 84px
-}
-
-a.link {
-       color: #3f4449;
-       font-weight: bold;
-       text-decoration: none;
-}
-
-a.link:hover {
-       text-decoration: underline;
-}
diff --git a/gui/baculum/themes/Baculum-v1/favicon.ico b/gui/baculum/themes/Baculum-v1/favicon.ico
deleted file mode 100644 (file)
index 19ca88b..0000000
Binary files a/gui/baculum/themes/Baculum-v1/favicon.ico and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/icon_close.png b/gui/baculum/themes/Baculum-v1/icon_close.png
deleted file mode 100644 (file)
index 9facce8..0000000
Binary files a/gui/baculum/themes/Baculum-v1/icon_close.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/icon_err.png b/gui/baculum/themes/Baculum-v1/icon_err.png
deleted file mode 100644 (file)
index 447e7c5..0000000
Binary files a/gui/baculum/themes/Baculum-v1/icon_err.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/icon_ok.png b/gui/baculum/themes/Baculum-v1/icon_ok.png
deleted file mode 100644 (file)
index dad2fa8..0000000
Binary files a/gui/baculum/themes/Baculum-v1/icon_ok.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/icon_refresh.png b/gui/baculum/themes/Baculum-v1/icon_refresh.png
deleted file mode 100644 (file)
index 54ba01b..0000000
Binary files a/gui/baculum/themes/Baculum-v1/icon_refresh.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/panel-border-bg.png b/gui/baculum/themes/Baculum-v1/panel-border-bg.png
deleted file mode 100644 (file)
index b682bd4..0000000
Binary files a/gui/baculum/themes/Baculum-v1/panel-border-bg.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-active.png b/gui/baculum/themes/Baculum-v1/step-active.png
deleted file mode 100644 (file)
index 3956ce2..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-active.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-bar.png b/gui/baculum/themes/Baculum-v1/step-bar.png
deleted file mode 100644 (file)
index 89fbd2b..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-bar.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-content.png b/gui/baculum/themes/Baculum-v1/step-content.png
deleted file mode 100644 (file)
index ee1d768..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-content.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-first-active.png b/gui/baculum/themes/Baculum-v1/step-first-active.png
deleted file mode 100644 (file)
index 3c13aee..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-first-active.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-first-next-active.png b/gui/baculum/themes/Baculum-v1/step-first-next-active.png
deleted file mode 100644 (file)
index 31cc1e4..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-first-next-active.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-first.png b/gui/baculum/themes/Baculum-v1/step-first.png
deleted file mode 100644 (file)
index d36d734..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-first.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-head.png b/gui/baculum/themes/Baculum-v1/step-head.png
deleted file mode 100644 (file)
index e7c0902..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-head.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-last-1.png b/gui/baculum/themes/Baculum-v1/step-last-1.png
deleted file mode 100644 (file)
index c8dde10..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-last-1.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-last-active-1.png b/gui/baculum/themes/Baculum-v1/step-last-active-1.png
deleted file mode 100644 (file)
index ad409c2..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-last-active-1.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step-prev-active.png b/gui/baculum/themes/Baculum-v1/step-prev-active.png
deleted file mode 100644 (file)
index cb5634d..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step-prev-active.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/step.png b/gui/baculum/themes/Baculum-v1/step.png
deleted file mode 100644 (file)
index fa82e90..0000000
Binary files a/gui/baculum/themes/Baculum-v1/step.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/wizard-bottom-1.png b/gui/baculum/themes/Baculum-v1/wizard-bottom-1.png
deleted file mode 100644 (file)
index c8f9e11..0000000
Binary files a/gui/baculum/themes/Baculum-v1/wizard-bottom-1.png and /dev/null differ
diff --git a/gui/baculum/themes/Baculum-v1/wizard-header-1.png b/gui/baculum/themes/Baculum-v1/wizard-header-1.png
deleted file mode 100644 (file)
index 9101fa1..0000000
Binary files a/gui/baculum/themes/Baculum-v1/wizard-header-1.png and /dev/null differ
index c9bf052dd870ead1184d607a47b4c313aa69b631..84770c44f0da7adfc2b3bdfef646ef1f009c35ee 100644 (file)
@@ -534,3 +534,14 @@ img.job_weather_icon {
        width: 32px;
        cursor: help;
 }
+
+/* API server OAuth2 progress bar */
+
+#progress_label {
+       position: absolute;
+       width: 100%;
+       top: 50%;
+       left: 50%;
+       transform: translate(-50%, -50%);
+       font-weight: bold;
+}