]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: New features and improvements to multi-user interface and restricted access
authorMarcin Haba <marcin.haba@bacula.pl>
Tue, 29 Sep 2020 04:16:33 +0000 (06:16 +0200)
committerMarcin Haba <marcin.haba@bacula.pl>
Tue, 29 Sep 2020 04:16:33 +0000 (06:16 +0200)
Implement the following functions on security page:
 - Console ACLs
 - OAuth2 clients
 - API hosts

25 files changed:
gui/baculum/protected/API/Class/BaculaConfig.php
gui/baculum/protected/API/Class/BaculumAPIServer.php
gui/baculum/protected/API/Class/Bconsole.php
gui/baculum/protected/API/JavaScript/misc.js
gui/baculum/protected/API/Pages/API/OAuth2Client.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/API/OAuth2Clients.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/API/endpoints.xml
gui/baculum/protected/API/openapi_baculum.json
gui/baculum/protected/Common/Class/Errors.php
gui/baculum/protected/Common/Class/OAuth2.php
gui/baculum/protected/Web/Class/BaculumAPIClient.php
gui/baculum/protected/Web/JavaScript/misc.js
gui/baculum/protected/Web/Lang/en/messages.mo
gui/baculum/protected/Web/Lang/en/messages.po
gui/baculum/protected/Web/Lang/ja/messages.mo
gui/baculum/protected/Web/Lang/ja/messages.po
gui/baculum/protected/Web/Lang/pl/messages.mo
gui/baculum/protected/Web/Lang/pl/messages.po
gui/baculum/protected/Web/Lang/pt/messages.mo
gui/baculum/protected/Web/Lang/pt/messages.po
gui/baculum/protected/Web/Pages/Security.page
gui/baculum/protected/Web/Pages/Security.php
gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.php
gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.tpl
gui/baculum/protected/Web/Portlets/BaculaConfigResources.php

index 49278af5b6d6d82a67f513b6fd83ca8f5428a954..b083469a1952019b6991fa203c4b7e1e8f0b431b 100644 (file)
@@ -59,21 +59,21 @@ class BaculaConfig extends ConfigFileModule {
         * @access public
         * @param string $component_type Bacula component type
         * @param array $config config
+        * @param string $file config file path
         * @return array validation result, validation output and write to config result
         */
-       public function setConfig($component_type, array $config) {
+       public function setConfig($component_type, array $config, $file = null) {
                $result = array('is_valid' => false, 'save_result' => false, 'output' => null);
                $config_content = $this->prepareConfig($config, self::CONFIG_FILE_FORMAT);
                $validation = $this->validateConfig($component_type, $config_content);
                $result['is_valid'] = $validation['is_valid'];
                $result['result'] = $validation['result'];
                if ($result['is_valid'] === true) {
-                       $tool_config = $this->getModule('api_config')->getJSONToolConfig($component_type);
-                       /**
-                        * @TODO: Add option to save config in a specific directory. Users may want
-                        * to see config and put it manually to production.
-                        */
-                       $result['save_result'] = $this->writeConfig($config, $tool_config['cfg'], self::CONFIG_FILE_FORMAT);
+                       if (is_null($file)) {
+                               $tool_config = $this->getModule('api_config')->getJSONToolConfig($component_type);
+                               $file = $tool_config['cfg'];
+                       }
+                       $result['save_result'] = $this->writeConfig($config, $file, self::CONFIG_FILE_FORMAT);
                }
                return $result;
        }
index bf567931f60d6edf272b85efb4780223b79b570c..568c2a1dd72325f63cb034daca82c2c673032da5 100644 (file)
@@ -340,7 +340,7 @@ abstract class BaculumAPIServer extends TPage {
        private function delete() {
                $id = null;
                if ($this->Request->contains('id')) {
-                       $id = intval($this->Request['id']);
+                       $id = $this->Request['id'];
                }
                $this->remove($id);
        }
index cd157501424539a69aadbfb64de00e188e71f4bc..3eccd2fe78d0c9f36632d12c6e605d928fa9c6a8 100644 (file)
@@ -44,17 +44,17 @@ class Bconsole extends APIModule {
        const PTYPE_CONFIRM_YES_CMD = 3;
        const PTYPE_CONFIRM_YES_BG_CMD = 4;
 
-       const BCONSOLE_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <<END_OF_DATA\ngui on\n%s\nquit\nEND_OF_DATA";
+       const BCONSOLE_COMMAND_PATTERN = "%s%s -c \"%s\" %s 2>&1 <<END_OF_DATA\ngui on\n%s\nquit\nEND_OF_DATA";
 
-       const BCONSOLE_BG_COMMAND_PATTERN = "echo 'gui on\n%s\nquit\n' | nohup %s%s -c %s %s >%s 2>&1 &";
+       const BCONSOLE_BG_COMMAND_PATTERN = "echo 'gui on\n%s\nquit\n' | nohup %s%s -c \"%s\" %s >%s 2>&1 &";
 
-       const BCONSOLE_CONFIRM_YES_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <<END_OF_DATA\ngui on\n%s\nyes\nquit\nEND_OF_DATA";
+       const BCONSOLE_CONFIRM_YES_COMMAND_PATTERN = "%s%s -c \"%s\" %s 2>&1 <<END_OF_DATA\ngui on\n%s\nyes\nquit\nEND_OF_DATA";
 
-       const BCONSOLE_CONFIRM_YES_BG_COMMAND_PATTERN = "echo 'gui on\n%s\nyes\nquit\n' | nohup %s%s -c %s %s >%s 2>&1 &";
+       const BCONSOLE_CONFIRM_YES_BG_COMMAND_PATTERN = "echo 'gui on\n%s\nyes\nquit\n' | nohup %s%s -c \"%s\" %s >%s 2>&1 &";
 
-       const BCONSOLE_API_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <<END_OF_DATA\ngui on\n.api 2 nosignal api_opts=o\n%s\nquit\nEND_OF_DATA";
+       const BCONSOLE_API_COMMAND_PATTERN = "%s%s -c \"%s\" %s 2>&1 <<END_OF_DATA\ngui on\n.api 2 nosignal api_opts=o\n%s\nquit\nEND_OF_DATA";
 
-       const BCONSOLE_DIRECTORS_PATTERN = "%s%s -c %s -l 2>&1";
+       const BCONSOLE_DIRECTORS_PATTERN = "%s%s -c \"%s\" -l 2>&1";
 
        const OUTPUT_FILE_PREFIX = 'output_';
 
index 22c9aa5d38602e22f2a36c8a02a2323e9a48302f..0b6a3396356558bfaf033cd49cf8abaaa7ba954f 100644 (file)
@@ -20,7 +20,8 @@ var OAuth2Scopes = [
        'schedules',
        'config',
        'status',
-       'actions'
+       'actions',
+       'oauth2'
 ];
 var set_scopes = function(field_id) {
        document.getElementById(field_id).value = OAuth2Scopes.join(' ');
diff --git a/gui/baculum/protected/API/Pages/API/OAuth2Client.php b/gui/baculum/protected/API/Pages/API/OAuth2Client.php
new file mode 100644 (file)
index 0000000..52fbbf4
--- /dev/null
@@ -0,0 +1,322 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.
+ */
+
+/**
+ * OAuth2 client endpoint.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category API
+ * @package Baculum API
+ */
+class OAuth2Client extends BaculumAPIServer {
+
+       public function get() {
+               $oauth2_client_id = $this->Request->contains('id') ? $this->Request['id'] : 0;
+               $client_id = $this->getModule('oauth2')->validateClientId($oauth2_client_id) ? $oauth2_client_id : null;
+               if (is_string($client_id)) {
+                       $oauth2_cfg = $this->getModule('oauth2_config')->getConfig($client_id);
+                       if (count($oauth2_cfg) > 0) {
+                               $this->output = $oauth2_cfg;
+                               $this->error = OAuth2Error::ERROR_NO_ERRORS;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+                       }
+               } else {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+               }
+       }
+
+       public function create($params) {
+               $oauth2 = $this->getModule('oauth2');
+               $oauth2_config = $this->getModule('oauth2_config');
+               $misc = $this->getModule('misc');
+               $oauth2_cfg = $oauth2_config->getConfig();
+
+               if (property_exists($params, 'client_id') && key_exists($params->client_id, $oauth2_cfg)) {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_ALREADY_EXISTS;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_ALREADY_EXISTS;
+                       return;
+               }
+
+               if (property_exists($params, 'client_id') && $oauth2->validateClientId($params->client_id)) {
+                       $oauth2_cfg[$params->client_id]['client_id'] = $params->client_id;
+               } else {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_ID;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CLIENT_ID;
+                       return;
+               }
+
+               if (property_exists($params, 'client_secret') && $oauth2->validateClientSecret($params->client_secret)) {
+                       $oauth2_cfg[$params->client_id]['client_secret'] = $params->client_secret;
+               } else {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET;
+                       return;
+               }
+
+               if (property_exists($params, 'redirect_uri') && $oauth2->validateRedirectUri($params->redirect_uri)) {
+                       $oauth2_cfg[$params->client_id]['redirect_uri'] = $params->redirect_uri;
+               } else {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI;
+                       return;
+               }
+
+               if (property_exists($params, 'scope') && $oauth2->validateScopes($params->scope)) {
+                       $oauth2_cfg[$params->client_id]['scope'] = $params->scope;
+               } else {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_SCOPE;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_SCOPE;
+                       return;
+               }
+
+               if (property_exists($params, 'bconsole_cfg_path')) {
+                       if ($misc->isValidPath($params->bconsole_cfg_path)) {
+                               $oauth2_cfg[$params->client_id]['bconsole_cfg_path'] = $params->bconsole_cfg_path;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH;
+                               return;
+                       }
+               } else {
+                       $oauth2_cfg[$params->client_id]['bconsole_cfg_path'] = '';
+               }
+
+               if (property_exists($params, 'name') && !empty($params->name)) {
+                       if ($misc->isValidName($params->name)) {
+                               $oauth2_cfg[$params->client_id]['name'] = $params->name;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_NAME;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_NAME;
+                               return;
+                       }
+               } else {
+                       $oauth2_cfg[$params->client_id]['name'] = '';
+               }
+
+               if (property_exists($params, 'console') && property_exists($params, 'director')) {
+                       if (!$misc->isValidName($params->console)) {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CONSOLE;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CONSOLE;
+                               return; 
+                       }
+                       if (!$misc->isValidName($params->director)) {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR;
+                               return; 
+                       }
+                       $bs = $this->getModule('bacula_setting');
+
+                       $dir_cfg = $bs->getConfig('bcons', 'Director', $params->director);
+                       if ($dir_cfg['exitcode'] != 0) {
+                               $this->output = $dir_cfg->output;
+                               $this->error = OAuth2Error::ERROR_INTERNAL_ERROR;
+                               return;
+                       }
+
+                       $console_cfg = $bs->getConfig('dir', 'Console', $params->console);
+                       if ($console_cfg['exitcode'] != 0) {
+                               $this->output = $console_cfg->output;
+                               $this->error = OAuth2Error::ERROR_INTERNAL_ERROR;
+                               return;
+                       }
+
+                       $cfg = [
+                               [
+                                       'Director' => [
+                                               'Name' => '"' . $dir_cfg['output']['Name'] . '"',
+                                               'DirPort' => $dir_cfg['output']['DirPort'],
+                                               'Address' => $dir_cfg['output']['Address'],
+                                               'Password' => 'XXXX'
+                                       ],
+                                       'Console' => [
+                                               'Name' => '"' . $console_cfg['output']['Name'] . '"',
+                                               'Password' => '"' . $console_cfg['output']['Password'] . '"'
+                                       ]
+                               ]
+                       ];
+                       $json_tools = $this->getModule('api_config')->getConfig('jsontools');
+                       $dir = $json_tools['bconfig_dir'];
+                       $file = sprintf('%s/bconsole-%s.cfg', $dir, $console_cfg['output']['Name']);
+                       $this->getModule('bacula_config')->setConfig('bcons', $cfg, $file);
+                       $oauth2_cfg[$params->client_id]['bconsole_cfg_path'] = $file;
+               }
+
+               // save config
+               $result = $oauth2_config->setConfig($oauth2_cfg);
+
+               if ($result) {
+                       $this->output = $oauth2_cfg;
+                       $this->error = OAuth2Error::ERROR_NO_ERRORS;
+               } else {
+                       $this->output = OAuth2Error::MSG_ERROR_INTERNAL_ERROR;
+                       $this->error = OAuth2Error::ERROR_INTERNAL_ERROR;
+               }
+       }
+
+       public function set($id, $params) {
+               $oauth2_client_id = property_exists($params, 'client_id') ? $params->client_id : 0;
+               $client_id = $this->getModule('oauth2')->validateClientId($oauth2_client_id) ? $oauth2_client_id : null;
+               if (!is_string($client_id)) {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+                       return;
+               }
+
+               $oauth2 = $this->getModule('oauth2');
+               $oauth2_config = $this->getModule('oauth2_config');
+               $misc = $this->getModule('misc');
+               $oauth2_cfg = $oauth2_config->getConfig();
+
+               if (!key_exists($client_id, $oauth2_cfg)) {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+                       return;
+               }
+
+               if (property_exists($params, 'client_secret')) {
+                       if ($oauth2->validateClientSecret($params->client_secret)) {
+                               $oauth2_cfg[$client_id]['client_secret'] = $params->client_secret;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET;
+                               return;
+                       }
+               }
+               if (property_exists($params, 'redirect_uri')) {
+                       if ($oauth2->validateRedirectUri($params->redirect_uri)) {
+                               $oauth2_cfg[$client_id]['redirect_uri'] = $params->redirect_uri;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI;
+                               return;
+                       }
+               }
+               if (property_exists($params, 'scope')) {
+                       if ($oauth2->validateScopes($params->scope)) {
+                               $oauth2_cfg[$client_id]['scope'] = $params->scope;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_SCOPE;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_SCOPE;
+                               return;
+                       }
+               }
+               if (property_exists($params, 'bconsole_cfg_path')) {
+                       if ($misc->isValidPath($params->bconsole_cfg_path)) {
+                               $oauth2_cfg[$client_id]['bconsole_cfg_path'] = $params->bconsole_cfg_path;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH;
+                               return;
+                       }
+               }
+               if (property_exists($params, 'name') && !empty($params->name)) {
+                       if ($misc->isValidName($params->name)) {
+                               $oauth2_cfg[$client_id]['name'] = $params->name;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_NAME;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_NAME;
+                               return;
+                       }
+               } else {
+                       $oauth2_cfg[$client_id]['name'] = '';
+               }
+               if (property_exists($params, 'console') && property_exists($params, 'director')) {
+                       if (!$misc->isValidName($params->console)) {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CONSOLE;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CONSOLE;
+                               return; 
+                       }
+                       if (!$misc->isValidName($params->director)) {
+                               $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR;
+                               $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR;
+                               return; 
+                       }
+                       $bs = $this->getModule('bacula_setting');
+
+                       $dir_cfg = $bs->getConfig('bcons', 'Director', $params->director);
+                       if ($dir_cfg['exitcode'] != 0) {
+                               $this->output = $dir_cfg->output;
+                               $this->error = OAuth2Error::ERROR_INTERNAL_ERROR;
+                               return;
+                       }
+
+                       $console_cfg = $bs->getConfig('dir', 'Console', $params->console);
+                       if ($console_cfg['exitcode'] != 0) {
+                               $this->output = $console_cfg->output;
+                               $this->error = OAuth2Error::ERROR_INTERNAL_ERROR;
+                               return;
+                       }
+
+                       $cfg = [
+                               [
+                                       'Director' => [
+                                               'Name' => '"' . $dir_cfg['output']['Name'] . '"',
+                                               'DirPort' => $dir_cfg['output']['DirPort'],
+                                               'Address' => $dir_cfg['output']['Address'],
+                                               'Password' => 'XXXX'
+                                       ],
+                                       'Console' => [
+                                               'Name' => '"' . $console_cfg['output']['Name'] . '"',
+                                               'Password' => '"' . $console_cfg['output']['Password'] . '"'
+                                       ]
+                               ]
+                       ];
+                       $json_tools = $this->getModule('api_config')->getConfig('jsontools');
+                       $dir = $json_tools['bconfig_dir'];
+                       $file = sprintf('%s/bconsole-%s.cfg', $dir, $console_cfg['output']['Name']);
+                       $this->getModule('bacula_config')->setConfig('bcons', $cfg, $file);
+                       $oauth2_cfg[$params->client_id]['bconsole_cfg_path'] = $file;
+               }
+
+               $result = $oauth2_config->setConfig($oauth2_cfg);
+               if ($result) {
+                       $this->output = $oauth2_cfg;
+                       $this->error = OAuth2Error::ERROR_NO_ERRORS;
+               } else {
+                       $this->output = OAuth2Error::MSG_ERROR_INTERNAL_ERROR;
+                       $this->error = OAuth2Error::ERROR_INTERNAL_ERROR;
+               }
+       }
+
+       public function remove($id) {
+               $oauth2 = $this->getModule('oauth2_config');
+               $oauth2_cfg = $oauth2->getConfig();
+               if (key_exists($id, $oauth2_cfg)) {
+                       unset($oauth2_cfg[$id]);
+                       $result = $oauth2->setConfig($oauth2_cfg);
+                       if ($result) {
+                               $this->output = [];
+                               $this->error = OAuth2Error::ERROR_NO_ERRORS;
+                       } else {
+                               $this->output = OAuth2Error::MSG_ERROR_INTERNAL_ERROR;
+                               $this->error = OAuth2Error::ERROR_INTERNAL_ERROR;
+                       }
+               } else {
+                       $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+                       $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST;
+               }
+       }
+}
+?>
diff --git a/gui/baculum/protected/API/Pages/API/OAuth2Clients.php b/gui/baculum/protected/API/Pages/API/OAuth2Clients.php
new file mode 100644 (file)
index 0000000..08d07c0
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.
+ */
+
+/**
+ * OAuth2 clients endpoint.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category API
+ * @package Baculum API
+ */
+class OAuth2Clients extends BaculumAPIServer {
+
+       public function get() {
+               $oauth2_cfg = $this->getModule('oauth2_config')->getConfig();
+               $this->output = array_values($oauth2_cfg);
+               $this->error = ClientError::ERROR_NO_ERRORS;
+       }
+}
+?>
index e40e59047fc9723284ab5f52b27870f1bd6e821c..2dfb161b9e74b32bdfa383766a4eef5b4d9e90f5 100644 (file)
@@ -93,4 +93,7 @@
        <url ServiceParameter="ComponentStatus" pattern="api/v1/status/{component}/" parameters.component="(director|storage|client)" />
        <!-- actions endpoints -->
        <url ServiceParameter="Actions" pattern="api/v1/actions/{component}/{action}/" parameters.component="(director|storage|client)" parameters.action="(start|stop|restart)" />
+       <!-- OAuth2 client endpoints -->
+       <url ServiceParameter="OAuth2Clients" pattern="api/v1/oauth2/clients/" />
+       <url ServiceParameter="OAuth2Client" pattern="api/v1/oauth2/clients/{id}/" parameters.id="[a-zA-Z0-9\-_]{32}" />
 </urls>
index c9b8f88a3ad7b612ea6a7321ada87370f2a2c8d1..f29491277767415ef19405425a67a1bfbcd5a1eb 100644 (file)
                        },
                        "Schedule": {
                                "$ref": "#/definitions/Schedule"
+                       },
+                       "OAuth2Clients": {
+                               "type": "array",
+                               "items": {
+                                       "$ref": "#/definitions/OAuth2Client"
+                               }
+                       },
+                       "OAuth2Client": {
+                               "$ref": "#/definitions/OAuth2Client"
                        }
                },
                "parameters": {
                                }]
                        }
                },
-               "/api/v1/status/director/": {
+               "/api/v1/status/director": {
                        "get": {
                                "tags": ["status"],
                                "summary": "Get director status",
                                ]
                        }
                },
-               "/api/v1/status/storage/": {
+               "/api/v1/status/storage": {
                        "get": {
                                "tags": ["status"],
                                "summary": "Get storage status",
                                ]
                        }
                },
-               "/api/v1/status/client/": {
+               "/api/v1/status/client": {
                        "get": {
                                "tags": ["status"],
                                "summary": "Get client status",
                                        }
                                ]
                        }
+               },
+               "/api/v1/oauth2/clients": {
+                       "get": {
+                               "tags": ["oauth2"],
+                               "summary": "OAuth2 client account list",
+                               "description": "Get OAuth2 client account list.",
+                               "responses": {
+                                       "200": {
+                                               "description": "List of OAuth2 clients properties",
+                                               "content": {
+                                                       "application/json": {
+                                                               "schema": {
+                                                                       "type": "object",
+                                                                       "properties": {
+                                                                               "output": {
+                                                                                       "$ref": "#/components/schemas/OAuth2Clients"
+                                                                               },
+                                                                               "error": {
+                                                                                       "type": "integer",
+                                                                                       "description": "Error code",
+                                                                                       "enum": [0, 6, 7, 1000]
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               },
+               "/api/v1/oauth2/clients/{client_id}": {
+                       "get": {
+                               "tags": ["oauth2"],
+                               "summary": "Specific OAuth2 client account config",
+                               "description": "Get specific OAuth2 client account config",
+                               "responses": {
+                                       "200": {
+                                               "description": "Specific OAuth2 client account config",
+                                               "content": {
+                                                       "application/json": {
+                                                               "schema": {
+                                                                       "type": "object",
+                                                                       "properties": {
+                                                                               "output": {
+                                                                                       "$ref": "#/components/schemas/OAuth2Client"
+                                                                               },
+                                                                               "error": {
+                                                                                       "type": "integer",
+                                                                                       "description": "Error code",
+                                                                                       "enum": [0, 6, 7, 120, 1000]
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               },
+                               "parameters": [
+                                       {
+                                               "name": "client_id",
+                                               "in": "path",
+                                               "required": true,
+                                               "description": "Client identifier (OAuth2 Client ID)",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       }
+                               ]
+                       },
+                       "put": {
+                               "tags": ["oauth2"],
+                               "summary": "Set OAuth2 client settings",
+                               "description": "Set specific OAuth2 client settings",
+                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "responses": {
+                                       "200": {
+                                               "description": "Set OAuth2 client settings",
+                                               "content": {
+                                                       "application/json": {
+                                                               "schema": {
+                                                                       "type": "object",
+                                                                       "properties": {
+                                                                               "output": {
+                                                                                       "type": "object",
+                                                                                       "description": "Updated OAuth2 client settings"
+                                                                               },
+                                                                               "error": {
+                                                                                       "type": "integer",
+                                                                                       "description": "Error code",
+                                                                                       "enum": [0, 6, 7, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 1000]
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               },
+                               "parameters": [
+                                       {
+                                               "name": "client_id",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "Client identifier (OAuth2 Client ID)",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "client_secret",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "OAuth2 client secret",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "redirect_uri",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "OAuth2 redirect URI (OAuth2 callback)",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "scope",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "Comma separated OAuth2 scopes",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "bconsole_cfg_path",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "Bconsole config file path",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "name",
+                                               "in": "body",
+                                               "required": false,
+                                               "description": "OAuth2 client account name",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       }
+                               ]
+                       },
+                       "post": {
+                               "tags": ["oauth2"],
+                               "summary": "Create OAuth2 client settings",
+                               "description": "Create specific OAuth2 client settings",
+                               "consumes": [ "application/x-www-form-urlencoded" ],
+                               "responses": {
+                                       "200": {
+                                               "description": "Create OAuth2 client settings",
+                                               "content": {
+                                                       "application/json": {
+                                                               "schema": {
+                                                                       "type": "object",
+                                                                       "properties": {
+                                                                               "output": {
+                                                                                       "type": "object",
+                                                                                       "description": "New OAuth2 client settings"
+                                                                               },
+                                                                               "error": {
+                                                                                       "type": "integer",
+                                                                                       "description": "Error code",
+                                                                                       "enum": [0, 6, 7, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 1000]
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               },
+                               "parameters": [
+                                       {
+                                               "name": "client_id",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "Client identifier (OAuth2 Client ID)",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "client_secret",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "OAuth2 client secret",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "redirect_uri",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "OAuth2 redirect URI (OAuth2 callback)",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "scope",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "Comma separated OAuth2 scopes",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "bconsole_cfg_path",
+                                               "in": "body",
+                                               "required": true,
+                                               "description": "Bconsole config file path",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       },
+                                       {
+                                               "name": "name",
+                                               "in": "body",
+                                               "required": false,
+                                               "description": "OAuth2 client account name",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       }
+                               ]
+                       },
+                       "delete": {
+                               "tags": ["oauth2"],
+                               "summary": "Delete OAuth2 client account",
+                               "description": "Delete OAuth2 client account.",
+                               "responses": {
+                                       "200": {
+                                               "description": "Delete OAuth2 client account",
+                                               "content": {
+                                                       "application/json": {
+                                                               "schema": {
+                                                                       "type": "object",
+                                                                       "properties": {
+                                                                               "output": {
+                                                                                       "type": "array",
+                                                                                       "items": {
+                                                                                       }
+                                                                               },
+                                                                               "error": {
+                                                                                       "type": "integer",
+                                                                                       "description": "Error code",
+                                                                                       "enum": [0, 6, 7, 120, 1000]
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               },
+                               "parameters": [
+                                       {
+                                               "name": "client_id",
+                                               "in": "path",
+                                               "required": true,
+                                               "description": "Client identifier (OAuth2 Client ID)",
+                                               "schema": {
+                                                       "type": "string"
+                                               }
+                                       }
+                               ]
+                       }
                }
        },
        "definitions": {
                                        "pattern": "[a-zA-Z0-9:.-_ ]+"
                                }
                        }
+               },
+               "OAuth2Client": {
+                       "type": "object",
+                       "properties": {
+                               "client_id": {
+                                       "description": "OAuth2 client identifier (Client ID)",
+                                       "type": "string",
+                                       "pattern": "[a-zA-Z0-9-_]{32}"
+                               },
+                               "client_secret": {
+                                       "description": "OAuth2 client secret",
+                                       "type": "string",
+                                       "pattern": "\\S{6,50}"
+                               },
+                               "redirect_uri": {
+                                       "description": "Redirect URI (OAuth2 callback)",
+                                       "type": "string"
+                               },
+                               "scope": {
+                                       "description": "Comma separated OAuth2 scopes",
+                                       "type": "string"
+                               },
+                               "bconsole_cfg_path": {
+                                       "description": "Dedicated Bconsole configuration file",
+                                       "type": "string"
+                               },
+                               "name": {
+                                       "description": "OAuth2 client name (optional)",
+                                       "type": "string"
+                               }
+                       }
                }
        }
 }
index 5e4c3e3cf1f7c1669ced7a1513253fd45e8608b7..e08a29143a68eaa7318ec548548186f478a30a8f 100644 (file)
@@ -160,7 +160,6 @@ class JSONToolsError extends GenericError {
        const ERROR_JSON_TOOLS_UNABLE_TO_PARSE_OUTPUT = 83;
        const ERROR_JSON_TOOL_NOT_CONFIGURED = 84;
 
-
        const MSG_ERROR_JSON_TOOLS_DISABLED = 'JSON tools support is disabled.';
        const MSG_ERROR_JSON_TOOLS_CONNECTION_PROBLEM = 'Problem with connection to a JSON tool.';
        const MSG_ERROR_JSON_TOOLS_WRONG_EXITCODE = 'JSON tool returned wrong exitcode.';
@@ -197,10 +196,34 @@ class ActionsError extends GenericError {
        const ERROR_ACTIONS_WRONG_EXITCODE = 112;
        const ERROR_ACTIONS_NOT_CONFIGURED = 113;
 
-
        const MSG_ERROR_ACTIONS_ACTION_DOES_NOT_EXIST = 'Action does not exist.';
        const MSG_ERROR_ACTIONS_DISABLED = 'Actions support is disabled.';
        const MSG_ERROR_ACTIONS_WRONG_EXITCODE = 'Action command returned wrong exitcode.';
        const MSG_ERROR_ACTIONS_NOT_CONFIGURED = 'Action is not configured.';
 }
+
+class OAuth2Error extends GenericError {
+
+       const ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST = 120;
+       const ERROR_OAUTH2_CLIENT_ALREADY_EXISTS = 121;
+       const ERROR_OAUTH2_CLIENT_INVALID_CLIENT_ID = 122;
+       const ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET = 123;
+       const ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI = 124;
+       const ERROR_OAUTH2_CLIENT_INVALID_SCOPE = 125;
+       const ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH = 126;
+       const ERROR_OAUTH2_CLIENT_INVALID_NAME = 127;
+       const ERROR_OAUTH2_CLIENT_INVALID_CONSOLE = 128;
+       const ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR = 129;
+
+       const MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST = 'OAuth2 client does not exist.';
+       const MSG_ERROR_OAUTH2_CLIENT_ALREADY_EXISTS = 'OAuth2 client already exists.';
+       const MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_ID = 'Invalid OAuth2 client ID.';
+       const MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET = 'Invalid OAuth2 client secret.';
+       const MSG_ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI = 'Invalid OAuth2 redirect URI.';
+       const MSG_ERROR_OAUTH2_CLIENT_INVALID_SCOPE = 'Invalid OAuth2 scope.';
+       const MSG_ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH = 'Invalid Bconsole config path.';
+       const MSG_ERROR_OAUTH2_CLIENT_INVALID_NAME = 'Invalid OAuth2 client name.';
+       const MSG_ERROR_OAUTH2_CLIENT_INVALID_CONSOLE = 'Invalid Console name.';
+       const MSG_ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR = 'Invalid Director name.';
+}
 ?>
index 7685eb773b0140a0662d9f8fbde924c99451a721..364c9cefc8f4a321aaecc29cf8184495fbb5d7fd 100644 (file)
@@ -47,6 +47,13 @@ abstract class OAuth2 extends CommonModule {
         */
        const CLIENT_SECRET_PATTERN = '^\S{6,50}$';
 
+       /**
+        * Very basic redirect URI validation pattern.
+        *
+        * @see https://tools.ietf.org/html/rfc3986#appendix-B
+        */
+       const REDIRECT_URI_PATTERN = '^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$';
+
        /**
         * Authorization ID (known also as 'authorization_code') regular expression pattern
         * allow to set hexadecimal value of the authorization ID with length equal 40 chars.
@@ -85,7 +92,7 @@ abstract class OAuth2 extends CommonModule {
        /**
         * Scope pattern.
         */
-       const SCOPE_PATTERN = '^[a-zA-Z:]+$';
+       const SCOPE_PATTERN = '^[a-zA-Z0-9:]+$';
 
        /**
         * State pattern.
@@ -138,6 +145,17 @@ abstract class OAuth2 extends CommonModule {
                return (preg_match('/' . self::CLIENT_SECRET_PATTERN . '/', $secret) === 1);
        }
 
+       /**
+        * Validate redirect URI syntax.
+        *
+        * @access public
+        * @param string $redirect_uri redirect URI value
+        * @return boolean true if redirect URI is valid, otherwise false
+        */
+       final public function validateRedirectUri($redirect_uri) {
+               return (preg_match('/' . self::REDIRECT_URI_PATTERN . '/', $redirect_uri) === 1);
+       }
+
        /**
         * Generate (pseudo-)random authorization identifier.
         * 
index 9501bb65e1c3912e9001f6a91ab56ab6288e79ea..0f83be61ebd69f0eca295142b610fa3f06e1ed9d 100644 (file)
@@ -129,7 +129,7 @@ class BaculumAPIClient extends WebModule {
                                $this->authToHost($host, $host_cfg);
                                $auth = OAuth2Record::findByPk($host);
                        }
-                       if (is_array($auth) && array_key_exists('tokens', $auth)) {
+                       if (is_array($auth) && key_exists('tokens', $auth) && is_array($auth['tokens'])) {
                                $headers[] = "Authorization: {$auth['tokens']['token_type']} {$auth['tokens']['access_token']}";
                        }
                }
index 5b9702fb8bde2c040865b2cfeea232424113ff28..e1a1e631dbba8e7e5a275b7ae815cea5e01d1e09 100644 (file)
@@ -369,6 +369,23 @@ function render_time_period(data, type, row) {
        return ret;
 }
 
+function render_string_short(data, type, row) {
+       ret = data;
+       if (type == 'display') {
+               var span = document.createElement('SPAN');
+               span.title = data;
+               if (data.length > 40) {
+                       span.textContent = data.substring(0, 40) + '...';
+               } else {
+                       span.textContent = data;
+               }
+               ret = span.outerHTML;
+       } else {
+               ret = data;
+       }
+       return ret;
+}
+
 function set_formatters() {
        Formatters.set_formatters();
 }
@@ -891,6 +908,26 @@ W3SubTabs = {
                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;
index 397ad936f9803dd00501b032001483dc85cdfa5e..368f5a97d8de2773c056f4027548930ca6165310 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ
index f85f69e03b6f04671d92dc4c7f06b15c4066105a..c4e676d7015a6bb6f8875e0fbd797388cb41e67e 100644 (file)
@@ -2992,3 +2992,72 @@ msgstr "This type of authentication is fully realized by Baculum Web. To authent
 
 msgid "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method."
 msgstr "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method."
+
+msgid "Console ACLs"
+msgstr "Console ACLs"
+
+msgid "Add new console"
+msgstr "Add new console"
+
+msgid "Edit console"
+msgstr "Edit console"
+
+msgid "Add console"
+msgstr "Add console"
+
+msgid "OAuth2 clients"
+msgstr "OAuth2 clients"
+
+msgid "Short name"
+msgstr "Short name"
+
+msgid "Add new OAuth2 client"
+msgstr "Add new OAuth2 client"
+
+msgid "Short name:"
+msgstr "Short name:"
+
+msgid "Add OAuth2 client account"
+msgstr "Add OAuth2 client account"
+
+msgid "set all scopes"
+msgstr "set all scopes"
+
+msgid "Edit OAuth2 client account"
+msgstr "Edit OAuth2 client account"
+
+msgid "Add new API host"
+msgstr "Add new API host"
+
+msgid "Get existing API host settings:"
+msgstr "Get existing API host settings:"
+
+msgid "Get existing OAuth2 client settings:"
+msgstr "Get existing OAuth2 client settings:"
+
+msgid "API hosts"
+msgstr "API hosts"
+
+msgid "Edit API host"
+msgstr "Edit API host"
+
+msgid "Create dedicated Bconsole config file:"
+msgstr "Create dedicated Bconsole config file:"
+
+msgid "Console ACL to use in new Bconsole config file:"
+msgstr "Console ACL to use in new Bconsole config file:"
+
+msgid "Director for Bconsole config:"
+msgstr "Director for Bconsole config:"
+
+msgid "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -&gt; OAuth2 client -&gt; API host -&gt; User account."
+msgstr "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -&gt; OAuth2 client -&gt; API host -&gt; User account."
+
+msgid "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API."
+msgstr "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API."
+
+msgid "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance."
+msgstr "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance."
+
+msgid "Set all CommandAcls used by Baculum Web"
+msgstr "Set all CommandAcls used by Baculum Web"
index 41fbdd7bc7ebf1c5eb0dc29e8d377b47e0f2865e..51c51b8f4701f6271dfb038ba6fd08a95eb09cad 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ
index e3b8c979757be338dd46e726b7923030999a6e86..51cb5ae7e0c5d88608c821e50404f4e7e2ccb3ce 100644 (file)
@@ -3078,3 +3078,72 @@ msgstr "This type of authentication is fully realized by Baculum Web. To authent
 
 msgid "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method."
 msgstr "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method."
+
+msgid "Console ACLs"
+msgstr "Console ACLs"
+
+msgid "Add new console"
+msgstr "Add new console"
+
+msgid "Edit console"
+msgstr "Edit console"
+
+msgid "Add console"
+msgstr "Add console"
+
+msgid "OAuth2 clients"
+msgstr "OAuth2 clients"
+
+msgid "Short name"
+msgstr "Short name"
+
+msgid "Add new OAuth2 client"
+msgstr "Add new OAuth2 client"
+
+msgid "Short name:"
+msgstr "Short name:"
+
+msgid "Add OAuth2 client account"
+msgstr "Add OAuth2 client account"
+
+msgid "set all scopes"
+msgstr "set all scopes"
+
+msgid "Edit OAuth2 client account"
+msgstr "Edit OAuth2 client account"
+
+msgid "Add new API host"
+msgstr "Add new API host"
+
+msgid "Get existing API host settings:"
+msgstr "Get existing API host settings:"
+
+msgid "Get existing OAuth2 client settings:"
+msgstr "Get existing OAuth2 client settings:"
+
+msgid "API hosts"
+msgstr "API hosts"
+
+msgid "Edit API host"
+msgstr "Edit API host"
+
+msgid "Create dedicated Bconsole config file:"
+msgstr "Create dedicated Bconsole config file:"
+
+msgid "Console ACL to use in new Bconsole config file:"
+msgstr "Console ACL to use in new Bconsole config file:"
+
+msgid "Director for Bconsole config:"
+msgstr "Director for Bconsole config:"
+
+msgid "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -&gt; OAuth2 client -&gt; API host -&gt; User account."
+msgstr "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -&gt; OAuth2 client -&gt; API host -&gt; User account."
+
+msgid "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API."
+msgstr "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API."
+
+msgid "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance."
+msgstr "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance."
+
+msgid "Set all CommandAcls used by Baculum Web"
+msgstr "Set all CommandAcls used by Baculum Web"
index 818f9e7d13ea20772564a1c5ae326d516dc6aaba..bbf162e61b8a2a81154e84e59b6a0268bedc9c02 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ
index 1d48068d41342851643d80e944a024bb049e24f1..8f4aef09b17270877e246fd3110b6656b90b5739 100644 (file)
@@ -3003,3 +3003,72 @@ msgstr "Ten typ uwierzytelniania jest w peÅ‚ni realizowany Baculum Web. Do uwier
 msgid "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method."
 msgstr "Ten typ uwierzytelniania jest realizowany przez zewnÄ™trznÄ… usÅ‚ugÄ™ katalogowÄ…. Do uwierzytelniania używa formularza logowania Baculum Web. Uwierzytelnianie basic serwera WWW może być wyłączone w tej metodzie."
 
+msgid "Console ACLs"
+msgstr "Konsole ACLs"
+
+msgid "Add new console"
+msgstr "Dodaj nowÄ… konsolÄ™"
+
+msgid "Edit console"
+msgstr "Edytuj konsolÄ™"
+
+msgid "Add console"
+msgstr "Dodaj konsolÄ™"
+
+msgid "OAuth2 clients"
+msgstr "Klienci OAuth2"
+
+msgid "Short name"
+msgstr "Nazwa"
+
+msgid "Add new OAuth2 client"
+msgstr "Dodaj nowego klienta OAuth2"
+
+msgid "Short name:"
+msgstr "Nazwa:"
+
+msgid "Add OAuth2 client account"
+msgstr "Dodaj konto klienta OAuth2"
+
+msgid "set all scopes"
+msgstr "ustaw wszystkie zakresy"
+
+msgid "Edit OAuth2 client account"
+msgstr "Edytuj konto klienta OAuth2"
+
+msgid "Add new API host"
+msgstr "Dodaj nowy host API"
+
+msgid "Get existing API host settings:"
+msgstr "Pobierz konfiguracjÄ™ istniejÄ…cego hosta API:"
+
+msgid "Get existing OAuth2 client settings:"
+msgstr "Pobierz konfiguracjÄ™ istniejÄ…cego klienta OAuth2:"
+
+msgid "API hosts"
+msgstr "Hosty API"
+
+msgid "Edit API host"
+msgstr "Edytuj host API"
+
+msgid "Create dedicated Bconsole config file:"
+msgstr "Stwórz dedykowany plik konfiguracyjny Bconsole:"
+
+msgid "Console ACL to use in new Bconsole config file:"
+msgstr "Konsole ACL do użycia w nowym pliku konfiguracyjnym:"
+
+msgid "Director for Bconsole config:"
+msgstr "ZarzÄ…dca dla pliku konfiguracyjnego Bconsole:"
+
+msgid "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -&gt; OAuth2 client -&gt; API host -&gt; User account."
+msgstr "Konsole ACL umożliwiajÄ… zdefiniowanie dostÄ™pnych zasobów dla użytkowników. Konsole sÄ… używane w konfiguracji Bacula po stronie hosta API. Jest możliwe przypisanie konsoli do klientów OAuth2, którzy mogÄ… być użyci w hostach API. Relacja przypisania jest: Console ACL -&gt; Klient OAuth2 -&gt; Host API -&gt; Konto użytkownika."
+
+
+msgid "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API."
+msgstr "Klienci OAuth2 sÄ… konfigurowani na hostach API. Aby stworzyć klienta OAuth2 z tej strony, potrzebujesz przypisać zakres 'oauth2' na koncie OAuth2 powiÄ…zanym z twoim obecnym kontem administratora."
+
+msgid "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance."
+msgstr "Hosty API definiujÄ… parametry połączenia do hostów z instancjami Baculum API. Możesz stworzyć połączenia do hostów API dedykowane dla okreÅ›lonych użytkowników poprzez przypisanie hostów API do  nich na zakÅ‚adce Użytkownicy. Jest możliwe utworzenie wielu połączeÅ„ do hostów API dla tej samej instancji Baculum API."
+
+msgid "Set all CommandAcls used by Baculum Web"
+msgstr "Ustaw wszystkie wartoÅ›ci CommandAcl używane przez Baculum Web"
index 3dd17eeb1d616bc7650c62bd0a9afb58009b4e9d..82d293853edfdcf85b579adf4484e452cb604aa2 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ
index 70cf4b9d861ab1806450a8933526fb0597ae54a7..dbf0f5a011732f1bda63b17258f57816e2618d7e 100644 (file)
@@ -3003,3 +3003,71 @@ msgstr "Esse tipo de autenticação Ã© totalmente realizado pelo Baculum Web. Pa
 msgid "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method."
 msgstr "Esse tipo de autenticação Ã© realizado por um serviço de diretório externo. Para autenticar, use o formulário de login do Baculum. A autenticação básica do servidor da web pode ser desativada nesse método."
 
+msgid "Console ACLs"
+msgstr "Console ACLs"
+
+msgid "Add new console"
+msgstr "Add new console"
+
+msgid "Edit console"
+msgstr "Edit console"
+
+msgid "Add console"
+msgstr "Add console"
+
+msgid "OAuth2 clients"
+msgstr "OAuth2 clients"
+
+msgid "Short name"
+msgstr "Short name"
+
+msgid "Add new OAuth2 client"
+msgstr "Add new OAuth2 client"
+
+msgid "Short name:"
+msgstr "Short name:"
+
+msgid "Add OAuth2 client account"
+msgstr "Add OAuth2 client account"
+
+msgid "set all scopes"
+msgstr "set all scopes"
+
+msgid "Edit OAuth2 client account"
+msgstr "Edit OAuth2 client account"
+
+msgid "Add new API host"
+msgstr "Add new API host"
+
+msgid "Get existing API host settings:"
+msgstr "Get existing API host settings:"
+
+msgid "Get existing OAuth2 client settings:"
+msgstr "Get existing OAuth2 client settings:"
+
+msgid "API hosts"
+msgstr "API hosts"
+
+msgid "Edit API host"
+msgstr "Edit API host"
+
+msgid "Create dedicated Bconsole config file:"
+msgstr "Create dedicated Bconsole config file:"
+
+msgid "Console ACL to use in new Bconsole config file:"
+msgstr "Console ACL to use in new Bconsole config file:"
+
+msgid "Director for Bconsole config:"
+msgstr "Director for Bconsole config:"
+
+msgid "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -&gt; OAuth2 client -&gt; API host -&gt; User account."
+msgstr "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -&gt; OAuth2 client -&gt; API host -&gt; User account."
+
+msgid "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API."
+msgstr "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API."
+
+msgid "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance."
+msgstr "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance."
+
+msgid "Set all CommandAcls used by Baculum Web"
+msgstr "Set all CommandAcls used by Baculum Web"
index 3b8d2d2332f4b7a2d7ae33e8e331b9ec42be5c0c..c774ef7223193ff7b68f5731109340144133b4fb 100644 (file)
@@ -10,6 +10,9 @@
                <button id="btn_user_security" type="button" class="w3-bar-item w3-button tab_btn w3-grey" onclick="W3Tabs.open(this.id, 'user_security');"><%[ Settings ]%></button>
                <button id="btn_user_list" type="button" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'user_list'); oUserList.table.responsive.recalc();"><%[ Users ]%></button>
                <button id="btn_role_list" type="button" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'role_list'); oRoleList.table.responsive.recalc();"><%[ Roles ]%></button>
+               <button id="btn_console_list" type="button" class="w3-bar-item w3-button tab_btn<%=empty($_SESSION['dir']) ? ' hide': ''%>" onclick="W3Tabs.open(this.id, 'console_list'); oConsoleList.table.responsive.recalc();"><%[ Console ACLs ]%></button>
+               <button id="btn_oauth2_client_list" type="button" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'oauth2_client_list'); oOAuth2ClientList.table.responsive.recalc();"><%[ OAuth2 clients ]%></button>
+               <button id="btn_api_host_list" type="button" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'api_host_list'); oAPIHostList.table.responsive.recalc();"><%[ API hosts ]%></button>
        </div>
 
        <div class="w3-container tab_item" id="user_security">
@@ -1443,7 +1446,6 @@ $(function() {
                                        >
                                                <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
                                        </com:TActiveLinkButton>
-                                       <i id="status_command_loading" class="fa fa-sync w3-spin" style="visibility: hidden;"></i>
                                </footer>
                        </div>
                        <com:TActiveHiddenField ID="UserWindowType" />
@@ -1866,7 +1868,6 @@ $(function() {
                                        >
                                                <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
                                        </com:TActiveLinkButton>
-                                       <i id="status_command_loading" class="fa fa-sync w3-spin" style="visibility: hidden;"></i>
                                </footer>
                        </div>
                        <com:TActiveHiddenField ID="RoleWindowType" />
@@ -1881,4 +1882,1326 @@ var bulk_actions_output_id = '<%=$this->SourceTemplateControl->BulkActions->Bulk
 </script>
        <com:TCallback ID="RemoveRolesAction" OnCallback="TemplateControl.removeRoles" />
        <com:Application.Web.Portlets.BulkActionsModal ID="BulkActions" />
+       <div class="w3-container tab_item" id="console_list" style="display: none">
+               <p class="w3-hide-small"><%[ The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -&gt; OAuth2 client -&gt; API host -&gt; User account. ]%></p>
+               <div class="w3-panel">
+                       <button type="button" id="add_console_btn" class="w3-button w3-green" onclick="oConsoles.load_console_window()"><i class="fa fa-plus"></i> &nbsp;<%[ Add new console ]%></a>
+               </div>
+               <table id="console_list_table" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom selectable" style="width: 100%">
+                       <thead>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Name ]%></th>
+                                       <th class="w3-center">Description</th>
+                                       <th class="w3-center">JobACL</th>
+                                       <th class="w3-center">ClientACL</th>
+                                       <th class="w3-center">StorageACL</th>
+                                       <th class="w3-center">ScheduleACL</th>
+                                       <th class="w3-center">RunACL</th>
+                                       <th class="w3-center">PoolACL</th>
+                                       <th class="w3-center">CommandACL</th>
+                                       <th class="w3-center">FileSetACL</th>
+                                       <th class="w3-center">CatalogACL</th>
+                                       <th class="w3-center">WhereACL</th>
+                                       <th class="w3-center">PluginOptionsACL</th>
+                                       <th class="w3-center">BackupClientACL</th>
+                                       <th class="w3-center">RestoreClientACL</th>
+                                       <th class="w3-center">DirectoryACL</th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </thead>
+                       <tbody id="console_list_body"></tbody>
+                       <tfoot>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Name ]%></th>
+                                       <th class="w3-center">Description</th>
+                                       <th class="w3-center">JobACL</th>
+                                       <th class="w3-center">ClientACL</th>
+                                       <th class="w3-center">StorageACL</th>
+                                       <th class="w3-center">ScheduleACL</th>
+                                       <th class="w3-center">RunACL</th>
+                                       <th class="w3-center">PoolACL</th>
+                                       <th class="w3-center">CommandACL</th>
+                                       <th class="w3-center">FileSetACL</th>
+                                       <th class="w3-center">CatalogACL</th>
+                                       <th class="w3-center">WhereACL</th>
+                                       <th class="w3-center">PluginOptionsACL</th>
+                                       <th class="w3-center">BackupClientACL</th>
+                                       <th class="w3-center">RestoreClientACL</th>
+                                       <th class="w3-center">DirectoryACL</th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </tfoot>
+               </table>
+               <p class="info w3-hide-medium w3-hide-small"><%[ Tip: Use left-click to select table row. Use CTRL + left-click to multiple row selection. Use SHIFT + left-click to add a range of rows to selection. ]%></p>
+<com:TCallback ID="ConsoleList" OnCallback="TemplateControl.setConsoleList" />
+<com:TCallback ID="LoadConsole" OnCallback="TemplateControl.loadConsoleWindow" />
+<com:TCallback ID="RemoveConsolesAction" OnCallback="TemplateControl.removeConsoles" />
+<script>
+var oConsoleList = {
+       ids: {
+               console_list: 'console_list_table'
+       },
+       actions: [
+               {
+                       action: 'remove',
+                       label: '<%[ Remove ]%>',
+                       value: 'Name',
+                       callback: <%=$this->RemoveConsolesAction->ActiveControl->Javascript%>
+               }
+       ],
+       data: [],
+       table: null,
+       table_toolbar: null,
+       init: function() {
+               if (!this.table) {
+                       this.set_table();
+                       this.set_bulk_actions();
+                       this.set_events();
+               } else {
+                       var page = this.table.page();
+                       this.table.clear().rows.add(this.data).draw();
+                       this.table.page(page).draw(false);
+                       this.table_toolbar.style.display = 'none';
+               }
+       },
+       set_events: function() {
+               document.getElementById(this.ids.console_list).addEventListener('click', function(e) {
+                       $(function() {
+                               this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none';
+                       }.bind(this));
+               }.bind(this));
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.console_list).DataTable({
+                       data: this.data,
+                       deferRender: true,
+                       dom: 'lB<"table_toolbar">frtip',
+                       stateSave: true,
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
+                       columns: [
+                               {
+                                       className: 'details-control',
+                                       orderable: false,
+                                       data: null,
+                                       defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+                               },
+                               {data: 'Name'},
+                               {
+                                       data: 'Description',
+                                       visible: false
+                               },
+                               {
+                                       data: 'JobAcl',
+                                       render: render_string_short
+                               },
+                               {
+                                       data: 'ClientAcl',
+                                       render: render_string_short
+                               },
+                               {
+                                       data: 'StorageAcl',
+                                       render: render_string_short
+                               },
+                               {
+                                       data: 'ScheduleAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'RunAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'PoolAcl',
+                                       render: render_string_short
+                               },
+                               {
+                                       data: 'CommandAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'FilesetAcl',
+                                       render: render_string_short
+                               },
+                               {
+                                       data: 'CatalogAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'WhereAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'PluginOptionsAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'BackupClientAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'RestoreClientAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'DirectoryAcl',
+                                       render: render_string_short,
+                                       visible: false
+                               },
+                               {
+                                       data: 'Name',
+                                       render: function (data, type, row) {
+                                               var btn_edit = document.createElement('BUTTON');
+                                               btn_edit.className = 'w3-button w3-green';
+                                               btn_edit.type = 'button';
+                                               var i_edit = document.createElement('I');
+                                               i_edit.className = 'fa fa-list-ul';
+                                               var label_edit = document.createTextNode(' <%[ Edit ]%>');
+                                               btn_edit.appendChild(i_edit);
+                                               btn_edit.innerHTML += '&nbsp';
+                                               btn_edit.style.marginRight = '8px';
+                                               btn_edit.appendChild(label_edit);
+                                               btn_edit.setAttribute('onclick', 'oConsoles.load_console_window(\'' + data + '\')');
+                                               return btn_edit.outerHTML;
+                                       }
+                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: 'control',
+                               orderable: false,
+                               targets: 0
+                       },
+                       {
+                               className: 'action_col',
+                               orderable: false,
+                               targets: [ 17 ]
+                       },
+                       {
+                               className: "dt-center",
+                               targets: [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]
+                       }],
+                       select: {
+                               style:    'os',
+                               selector: 'td:not(:last-child):not(:first-child)',
+                               blurable: false
+                       },
+                       order: [1, 'asc']
+               });
+       },
+       set_bulk_actions: function() {
+               this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+                       actions: '<%[ Actions ]%>',
+                       ok: '<%[ OK ]%>'
+               });
+       }
+};
+
+var oConsoles = {
+       load_console_window: function(name) {
+               var title_add = document.getElementById('console_window_title_add');
+               var title_edit = document.getElementById('console_window_title_edit');
+               var console_win_type = document.getElementById('<%=$this->ConsoleWindowType->ClientID%>');
+               var all_command_acls = document.getElementById('<%=$this->ConsoleBaculumConfig->ClientID%>');
+               // callback is sent both for new and edit console because there is realized
+               // checking if password is allowed to set or not
+               var cb = <%=$this->LoadConsole->ActiveControl->Javascript%>;
+               cb.setCallbackParameter(name);
+               cb.dispatch();
+               if (name) {
+                       // edit existing console
+                       title_add.style.display = 'none';
+                       title_edit.style.display = 'inline-block';
+                       all_command_acls.style.display = 'none';
+                       console_win_type.value = 'edit';
+               } else {
+                       // add new console
+                       title_add.style.display = 'inline-block';
+                       title_edit.style.display = 'none';
+                       all_command_acls.style.display = '';
+                       console_win_type.value = 'add';
+               }
+               document.getElementById('console_window').style.display = 'block';
+       },
+       load_console_list: function() {
+               var cb = <%=$this->ConsoleList->ActiveControl->Javascript%>;
+               cb.dispatch();
+       },
+       load_console_list_cb: function(list) {
+               oConsoleList.data = list;
+               oConsoleList.init();
+       },
+       save_console_cb: function() {
+               this.load_console_list();
+               document.getElementById('console_window').style.display = 'none';
+       }
+}
+
+$(function() {
+       oConsoles.load_console_list();
+});
+               </script>
+       </div>
+       <div id="console_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('console_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                               <h2 id="console_window_title_add" style="display: none"><%[ Add console ]%></h2>
+                               <h2 id="console_window_title_edit" style="display: none"><%[ Edit console ]%></h2>
+                       </header>
+                       <div class="w3-container w3-margin-left w3-margin-right w3-margin-top w3-text-teal">
+                               <com:TActiveLinkButton
+                                       ID="ConsoleBaculumConfig"
+                                       CssClass="w3-right"
+                                       OnCommand="setAllCommandAcls"
+                                       CommandParameter="save"
+                               >
+                                       <i class="fas fa-globe"></i> &nbsp;<%[ Set all CommandAcls used by Baculum Web ]%>
+                               </com:TActiveLinkButton>
+                               <com:Application.Web.Portlets.BaculaConfigDirectives
+                                       ID="ConsoleConfig"
+                                       ComponentType="dir"
+                                       ResourceType="Console"
+                                       ShowRemoveButton="false"
+                                       ShowCancelButton="false"
+                                       ShowBottomButtons="false"
+                                       SaveDirectiveActionOk="oConsoles.save_console_cb();"
+                                       OnSave="loadOAuth2ClientConsole"
+                               />
+                       </div>
+               </div>
+               <com:TActiveHiddenField ID="ConsoleWindowType" />
+       </div>
+       <div class="w3-container tab_item" id="oauth2_client_list" style="display: none">
+               <p class="w3-hide-small"><%[ The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API. ]%></p>
+               <div id="oauth2_client_btn" class="w3-panel">
+                       <button type="button" id="add_oauth2_client_btn" class="w3-button w3-green" onclick="oOAuth2Clients.load_oauth2_client_window()"><i class="fa fa-plus"></i> &nbsp;<%[ Add new OAuth2 client ]%></a>
+               </div>
+               <table id="oauth2_client_list_table" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom selectable" style="width: 100%">
+                       <thead>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Short name ]%></th>
+                                       <th class="w3-center">OAuth2 Client ID</th>
+                                       <th class="w3-center">Redirect URI</th>
+                                       <th class="w3-center">Scopes</th>
+                                       <th class="w3-center">Dedicated BConsole config</th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </thead>
+                       <tbody id="oauth2_client_list_body"></tbody>
+                       <tfoot>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Short name ]%></th>
+                                       <th class="w3-center">OAuth2 Client ID</th>
+                                       <th class="w3-center">Redirect URI</th>
+                                       <th class="w3-center">Scopes</th>
+                                       <th class="w3-center">Dedicated BConsole config</th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </tfoot>
+               </table>
+               <p id="oauth2_client_table_footer" class="info w3-hide-medium w3-hide-small"><%[ Tip: Use left-click to select table row. Use CTRL + left-click to multiple row selection. Use SHIFT + left-click to add a range of rows to selection. ]%></p>
+<com:TCallback ID="OAuth2ClientList" OnCallback="TemplateControl.setOAuth2ClientList" />
+<com:TCallback ID="LoadOAuth2Client" OnCallback="TemplateControl.loadOAuth2ClientWindow" />
+<com:TCallback ID="RemoveOAuth2ClientsAction" OnCallback="TemplateControl.removeOAuth2Clients" />
+<script>
+var oOAuth2ClientList = {
+       ids: {
+               oauth2_client_list: 'oauth2_client_list_table'
+       },
+       actions: [
+               {
+                       action: 'remove',
+                       label: '<%[ Remove ]%>',
+                       value: 'client_id',
+                       callback: <%=$this->RemoveOAuth2ClientsAction->ActiveControl->Javascript%>
+               }
+       ],
+       data: [],
+       table: null,
+       table_toolbar: null,
+       init: function() {
+               if (!this.table) {
+                       this.set_table();
+                       this.set_bulk_actions();
+                       this.set_events();
+               } else {
+                       var page = this.table.page();
+                       this.table.clear().rows.add(this.data).draw();
+                       this.table.page(page).draw(false);
+                       this.table_toolbar.style.display = 'none';
+               }
+       },
+       set_events: function() {
+               document.getElementById(this.ids.oauth2_client_list).addEventListener('click', function(e) {
+                       $(function() {
+                               this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none';
+                       }.bind(this));
+               }.bind(this));
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.oauth2_client_list).DataTable({
+                       data: this.data,
+                       deferRender: true,
+                       dom: 'lB<"table_toolbar">frtip',
+                       stateSave: true,
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
+                       columns: [
+                               {
+                                       className: 'details-control',
+                                       orderable: false,
+                                       data: null,
+                                       defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+                               },
+                               {data: 'name'},
+                               {data: 'client_id'},
+                               {
+                                       data: 'redirect_uri',
+                                       render: render_string_short
+                               },
+                               {
+                                       data: 'scope',
+                                       render: render_string_short
+                               },
+                               {
+                                       data: 'bconsole_cfg_path',
+                                       render: function(data, type, row) {
+                                               var ret;
+                                               if (type == 'display') {
+                                                       ret = '';
+                                                       if (data) {
+                                                               var check = document.createElement('I');
+                                                               check.className = 'fas fa-check';
+                                                               ret = check.outerHTML;
+                                                       }
+                                               } else {
+                                                       ret = data;
+                                               }
+                                               return ret;
+                                       }
+                               },
+                               {
+                                       data: 'client_id',
+                                       render: function (data, type, row) {
+                                               var btn_edit = document.createElement('BUTTON');
+                                               btn_edit.className = 'w3-button w3-green';
+                                               btn_edit.type = 'button';
+                                               var i_edit = document.createElement('I');
+                                               i_edit.className = 'fa fa-list-ul';
+                                               var label_edit = document.createTextNode(' <%[ Edit ]%>');
+                                               btn_edit.appendChild(i_edit);
+                                               btn_edit.innerHTML += '&nbsp';
+                                               btn_edit.style.marginRight = '8px';
+                                               btn_edit.appendChild(label_edit);
+                                               btn_edit.setAttribute('onclick', 'oOAuth2Clients.load_oauth2_client_window(\'' + data + '\')');
+                                               return btn_edit.outerHTML;
+                                       }
+                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: 'control',
+                               orderable: false,
+                               targets: 0
+                       },
+                       {
+                               className: 'action_col',
+                               orderable: false,
+                               targets: [ 6 ]
+                       },
+                       {
+                               className: "dt-center",
+                               targets: [ 2, 3, 4, 5 ]
+                       }],
+                       select: {
+                               style:    'os',
+                               selector: 'td:not(:last-child):not(:first-child)',
+                               blurable: false
+                       },
+                       order: [1, 'asc']
+               });
+       },
+       set_bulk_actions: function() {
+               this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+                       actions: '<%[ Actions ]%>',
+                       ok: '<%[ OK ]%>'
+               });
+       }
+};
+
+var oOAuth2Clients = {
+       load_oauth2_client_window: function(name) {
+               var title_add = document.getElementById('oauth2_client_window_title_add');
+               var title_edit = document.getElementById('oauth2_client_window_title_edit');
+               var client_id = document.getElementById('<%=$this->OAuth2ClientClientId->ClientID%>');
+               var generate_client_id = document.getElementById('oauth2_client_generate_client_id_link');
+               var oauth2_client_win_type = document.getElementById('<%=$this->OAuth2ClientWindowType->ClientID%>');
+               // callback is sent both for new and edit oauth2_client because there is realized
+               // checking if password is allowed to set or not
+               var cb = <%=$this->LoadOAuth2Client->ActiveControl->Javascript%>;
+               cb.setCallbackParameter(name);
+               cb.dispatch();
+               if (name) {
+                       // edit existing oauth2_client
+                       title_add.style.display = 'none';
+                       title_edit.style.display = 'inline-block';
+                       oauth2_client_win_type.value = 'edit';
+                       generate_client_id.style.display = 'none';
+                       client_id.setAttribute('readonly', '');
+               } else {
+                       // add new oauth2_client
+                       title_add.style.display = 'inline-block';
+                       title_edit.style.display = 'none';
+                       oauth2_client_win_type.value = 'add';
+                       generate_client_id.style.display = '';
+                       client_id.removeAttribute('readonly');
+                       this.clear_oauth2_client_window();
+                       document.getElementById('<%=$this->OAuth2ClientRedirectURI->ClientID%>').value = document.location.origin + '/web/redirect';
+               }
+               document.getElementById('<%=$this->OAuth2ClientBconsoleCreate->ClientID%>').checked = false;
+               document.getElementById('oauth2_client_console').style.display = 'none';
+               document.getElementById('oauth2_client_bconsole_cfg').style.display = '';
+               document.getElementById('oauth2_client_window').style.display = 'block';
+               document.getElementById('oauth2_client_error').style.display = 'none';
+       },
+       load_oauth2_client_list: function() {
+               var cb = <%=$this->OAuth2ClientList->ActiveControl->Javascript%>;
+               cb.dispatch();
+       },
+       load_oauth2_client_list_cb: function(list, error) {
+               if (error == 0) {
+                       oOAuth2ClientList.data = list;
+                       oOAuth2ClientList.init();
+               } else if (error == 7) {
+                       oOAuth2Clients.hide_oauth2_client_func();
+               }
+       },
+       clear_oauth2_client_window: function() {
+               [
+                       '<%=$this->OAuth2ClientClientId->ClientID%>',
+                       '<%=$this->OAuth2ClientClientSecret->ClientID%>',
+                       '<%=$this->OAuth2ClientRedirectURI->ClientID%>',
+                       '<%=$this->OAuth2ClientScope->ClientID%>',
+                       '<%=$this->OAuth2ClientBconsoleCfgPath->ClientID%>',
+                       '<%=$this->OAuth2ClientBconsoleCfgPath->ClientID%>',
+                       '<%=$this->OAuth2ClientName->ClientID%>'
+               ].forEach(function(id) {
+                       document.getElementById(id).value = '';
+               });
+       },
+       save_oauth2_client_cb: function(result) {
+               if (result.error == 0) {
+                       document.getElementById('oauth2_client_error').style.display = 'none';
+                       document.getElementById('oauth2_client_window').style.display = 'none';
+               } else {
+                       document.getElementById('oauth2_client_error').textContent = result.output;
+                       document.getElementById('oauth2_client_error').style.display = '';
+               }
+       },
+       hide_oauth2_client_func: function() {
+               [
+                       'oauth2_client_btn',
+                       'oauth2_client_list_table',
+                       'oauth2_client_table_footer'
+               ].forEach(function(id) {
+                       document.getElementById(id).style.display = 'none';
+               });
+       }
+}
+
+$(function() {
+       oOAuth2Clients.load_oauth2_client_list();
+});
+               </script>
+       </div>
+       <div id="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('oauth2_client_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                               <h2 id="oauth2_client_window_title_add" style="display: none"><%[ Add OAuth2 client account ]%></h2>
+                               <h2 id="oauth2_client_window_title_edit" style="display: none"><%[ Edit OAuth2 client account ]%></h2>
+                       </header>
+                       <div class="w3-container w3-margin-left w3-margin-right w3-margin-top w3-text-teal">
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientClientId" Text="<%[ OAuth2 Client ID: ]%>" /></div>
+                                       <div class="w3-half">
+                                               <com:TActiveTextBox
+                                                       ID="OAuth2ClientClientId"
+                                                       AutoPostBack="false"
+                                                       CausesValidation="false"
+                                                       MaxLength="32"
+                                                       CssClass="w3-input w3-border"
+                                               />
+                                               <com:TRequiredFieldValidator
+                                                       ValidationGroup="OAuth2ClientGroup"
+                                                       ControlToValidate="OAuth2ClientClientId"
+                                                       ErrorMessage="<%[ Please enter Client ID. ]%>"
+                                                       ControlCssClass="field_invalid"
+                                                       Display="Dynamic"
+                                               >
+                                               </com:TRequiredFieldValidator>
+                                               <com:TRegularExpressionValidator
+                                                       ValidationGroup="OAuth2ClientGroup"
+                                                       ControlToValidate="OAuth2ClientClientId"
+                                                       ControlCssClass="field_invalid"
+                                                       RegularExpression="<%=OAuth2::CLIENT_ID_PATTERN%>"
+                                                       ErrorMessage="<%[ Invalid Client ID value. Client ID may contain a-z A-Z 0-9 - _ characters. ]%>"
+                                                       Display="Dynamic"
+                                                       />
+                                               <a id="oauth2_client_generate_client_id_link" href="javascript:void(0)" onclick="document.getElementById('<%=$this->OAuth2ClientClientId->ClientID%>').value = get_random_string('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 32);" style="display: none"><%[ generate ]%></a>
+                                       </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientClientSecret" Text="<%[ OAuth2 Client Secret: ]%>" /></div>
+                                       <div class="w3-half">
+                                               <com:TActiveTextBox
+                                                       ID="OAuth2ClientClientSecret"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                                       MaxLength="50"
+                                               />
+                                               <com:TRequiredFieldValidator
+                                                       ValidationGroup="OAuth2ClientGroup"
+                                                       ControlToValidate="OAuth2ClientClientSecret"
+                                                       ErrorMessage="<%[ Please enter Client Secret. ]%>"
+                                                       ControlCssClass="field_invalid"
+                                                       Display="Dynamic"
+                                               />
+                                               <com:TRegularExpressionValidator
+                                                       ValidationGroup="OAuth2ClientGroup"
+                                                       ControlToValidate="OAuth2ClientClientSecret"
+                                                       ControlCssClass="field_invalid"
+                                                       RegularExpression="<%=OAuth2::CLIENT_SECRET_PATTERN%>"
+                                                       ErrorMessage="<%[ Invalid Client Secret value. Client Secret may contain any character that is not a whitespace character. ]%>"
+                                                       Display="Dynamic"
+                                               />
+                                               <a href="javascript:void(0)" onclick="document.getElementById('<%=$this->OAuth2ClientClientSecret->ClientID%>').value = get_random_string('ABCDEFabcdef0123456789', 40); return false;"><%[ generate ]%></a>
+                                       </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientRedirectURI" Text="<%[ OAuth2 Redirect URI (example: https://baculumgui:9095/web/redirect): ]%>" /></div>
+                                       <div class="w3-half">
+                                               <com:TActiveTextBox
+                                                       ID="OAuth2ClientRedirectURI"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                               />
+                                               <com:TRequiredFieldValidator
+                                                       ValidationGroup="OAuth2ClientGroup"
+                                                       ControlToValidate="OAuth2ClientRedirectURI"
+                                                       ControlCssClass="field_invalid"
+                                                       ErrorMessage="<%[ Please enter Redirect URI. ]%>"
+                                                       Display="Dynamic"
+                                               />
+                                       </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientScope" Text="<%[ OAuth2 scopes (space separated): ]%>" /></div>
+                                       <div class="w3-half">
+                                               <com:TActiveTextBox
+                                                       ID="OAuth2ClientScope"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                                       TextMode="MultiLine"
+                                               />
+                                               <a href="javascript:void(0)" onclick="set_scopes('<%=$this->OAuth2ClientScope->ClientID%>');" style="vertical-align: top"><%[ set all scopes ]%></a>
+                                               <com:TRequiredFieldValidator
+                                                       ValidationGroup="OAuth2ClientGroup"
+                                                       ControlToValidate="OAuth2ClientScope"
+                                                       ControlCssClass="field_invalid"
+                                                       ErrorMessage="<%[ Please enter OAuth2 scopes. ]%>"
+                                                       Display="Dynamic"
+                                               />
+                                       </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
+                               </div>
+                               <div class="w3-row w3-section" id="oauth2_client_bconsole_create">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientBconsoleCreate" Text="<%[ Create dedicated Bconsole config file: ]%>" /></div>
+                                       <div class="w3-half">
+                                               <com:TActiveCheckBox
+                                                       ID="OAuth2ClientBconsoleCreate"
+                                                       CssClass="w3-check"
+                                                       CausesValidation="false"
+                                                       Attributes.onclick="$('#oauth2_client_console').slideToggle('fast');$('#oauth2_client_bconsole_cfg').slideToggle('fast');"
+                                                       AutoPostBack="false"
+                                               />
+                                       </div>
+                               </div>
+                               <div id="oauth2_client_console" style="display: none">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientConsole" Text="<%[ Console ACL to use in new Bconsole config file: ]%>" /></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveDropDownList
+                                                               ID="OAuth2ClientConsole"
+                                                               CssClass="w3-select w3-border"
+                                                               CausesValidation="false"
+                                                               AutoPostBack="false"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientDirector" Text="<%[ Director for Bconsole config: ]%>" /></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveDropDownList
+                                                               ID="OAuth2ClientDirector"
+                                                               CssClass="w3-select w3-border"
+                                                               CausesValidation="false"
+                                                               AutoPostBack="false"
+                                                       />
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section" id="oauth2_client_bconsole_cfg">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientBconsoleCfgPath" Text="<%[ Dedicated Bconsole config file path: ]%>" /></div>
+                                       <div class="w3-half">
+                                               <com:TActiveTextBox
+                                                       ID="OAuth2ClientBconsoleCfgPath"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                               />
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="OAuth2ClientName" Text="<%[ Short name: ]%>" /></div>
+                                       <div class="w3-half">
+                                               <com:TActiveTextBox
+                                                       ID="OAuth2ClientName"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                               />
+                                       </div>
+                               </div>
+                       </div>
+                       <footer class="w3-container w3-center">
+                               <button type="button" class="w3-button w3-red" onclick="document.getElementById('oauth2_client_window').style.display = 'none';"><i class="fas fa-times"></i> &nbsp;<%[ Cancel ]%></button>
+                               <com:TActiveLinkButton
+                                       ID="OAuth2ClientSave"
+                                       ValidationGroup="OAuth2ClientGroup"
+                                       CausesValidation="true"
+                                       OnCallback="saveOAuth2Client"
+                                       CssClass="w3-button w3-section w3-teal w3-padding"
+                               >
+                                       <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TActiveLinkButton>
+                               <div id="oauth2_client_error" class="w3-red" style="display: none"></div>
+                       </footer>
+               </div>
+               <com:TActiveHiddenField ID="OAuth2ClientWindowType" />
+       </div>
+       <div class="w3-container tab_item" id="api_host_list" style="display: none">
+               <p class="w3-hide-small"><%[ The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance. ]%></p>
+               <div class="w3-panel">
+                       <button type="button" id="add_api_host_btn" class="w3-button w3-green" onclick="oAPIHosts.load_api_host_window()"><i class="fa fa-plus"></i> &nbsp;<%[ Add new API host ]%></a>
+               </div>
+               <table id="api_host_list_table" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom selectable" style="width: 100%">
+                       <thead>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Short name ]%></th>
+                                       <th class="w3-center">Protocol</th>
+                                       <th class="w3-center">IP address/hostname</th>
+                                       <th class="w3-center">Port</th>
+                                       <th class="w3-center">Access method</th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </thead>
+                       <tbody id="api_host_list_body"></tbody>
+                       <tfoot>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Short name ]%></th>
+                                       <th class="w3-center">Protocol</th>
+                                       <th class="w3-center">IP address/hostname</th>
+                                       <th class="w3-center">Port</th>
+                                       <th class="w3-center">Access method</th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </tfoot>
+               </table>
+               <p class="info w3-hide-medium w3-hide-small"><%[ Tip: Use left-click to select table row. Use CTRL + left-click to multiple row selection. Use SHIFT + left-click to add a range of rows to selection. ]%></p>
+<com:TCallback ID="APIHostList" OnCallback="TemplateControl.setAPIHostList" />
+<com:TCallback ID="LoadAPIHost" OnCallback="TemplateControl.loadAPIHostWindow" />
+<com:TCallback ID="RemoveAPIHostsAction" OnCallback="TemplateControl.removeAPIHosts" />
+<script>
+var oAPIHostList = {
+       ids: {
+               api_host_list: 'api_host_list_table'
+       },
+       actions: [
+               {
+                       action: 'remove',
+                       label: '<%[ Remove ]%>',
+                       value: 'name',
+                       callback: <%=$this->RemoveAPIHostsAction->ActiveControl->Javascript%>
+               }
+       ],
+       data: [],
+       table: null,
+       table_toolbar: null,
+       init: function() {
+               if (!this.table) {
+                       this.set_table();
+                       this.set_bulk_actions();
+                       this.set_events();
+               } else {
+                       var page = this.table.page();
+                       this.table.clear().rows.add(this.data).draw();
+                       this.table.page(page).draw(false);
+                       oAPIHostList.set_filters(this.table);
+                       this.table_toolbar.style.display = 'none';
+               }
+       },
+       set_events: function() {
+               document.getElementById(this.ids.api_host_list).addEventListener('click', function(e) {
+                       $(function() {
+                               this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none';
+                       }.bind(this));
+               }.bind(this));
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.api_host_list).DataTable({
+                       data: this.data,
+                       deferRender: true,
+                       dom: 'lB<"table_toolbar">frtip',
+                       stateSave: true,
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
+                       columns: [
+                               {
+                                       className: 'details-control',
+                                       orderable: false,
+                                       data: null,
+                                       defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+                               },
+                               {data: 'name'},
+                               {data: 'protocol'},
+                               {data: 'address'},
+                               {data: 'port'},
+                               {
+                                       data: 'auth_type',
+                                       render: function (data, type, row) {
+                                               var at = data;
+                                               if (at == 'basic') {
+                                                       at = 'Basic';
+                                               } else if (at == 'oauth2') {
+                                                       at = 'OAuth2';
+                                               }
+                                               return at;
+                                       }
+                               },
+                               {
+                                       data: 'name',
+                                       render: function (data, type, row) {
+                                               var btn_edit = document.createElement('BUTTON');
+                                               btn_edit.className = 'w3-button w3-green';
+                                               btn_edit.type = 'button';
+                                               var i_edit = document.createElement('I');
+                                               i_edit.className = 'fa fa-list-ul';
+                                               var label_edit = document.createTextNode(' <%[ Edit ]%>');
+                                               btn_edit.appendChild(i_edit);
+                                               btn_edit.innerHTML += '&nbsp';
+                                               btn_edit.style.marginRight = '8px';
+                                               btn_edit.appendChild(label_edit);
+                                               btn_edit.setAttribute('onclick', 'oAPIHosts.load_api_host_window(\'' + data + '\')');
+                                               return btn_edit.outerHTML;
+                                       }
+                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: 'control',
+                               orderable: false,
+                               targets: 0
+                       },
+                       {
+                               className: 'action_col',
+                               orderable: false,
+                               targets: [ 6 ]
+                       },
+                       {
+                               className: "dt-center",
+                               targets: [ 2, 3, 4, 5 ]
+                       }],
+                       select: {
+                               style:    'os',
+                               selector: 'td:not(:last-child):not(:first-child)',
+                               blurable: false
+                       },
+                       order: [1, 'asc'],
+                       initComplete: function () {
+                               oAPIHostList.set_filters(this.api());
+                       }
+               });
+       },
+       set_filters: function(api) {
+               api.columns([2, 3, 4, 5]).every(function () {
+                       var column = this;
+                       var select = $('<select><option value=""></option></select>')
+                       .appendTo($(column.footer()).empty())
+                       .on('change', function () {
+                               var val = dtEscapeRegex(
+                                       $(this).val()
+                               );
+                               column
+                               .search(val ? '^' + val + '$' : '', true, false)
+                               .draw();
+                       });
+                       column.cells('', column[0]).render('display').unique().sort().each(function(d, j) {
+                               if (column.search() == '^' + dtEscapeRegex(d) + '$') {
+                                       select.append('<option value="' + d + '" selected>' + d + '</option>');
+                               } else if(d) {
+                                       select.append('<option value="' + d + '">' + d + '</option>');
+                               }
+                       });
+               });
+       },
+       set_bulk_actions: function() {
+               this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+                       actions: '<%[ Actions ]%>',
+                       ok: '<%[ OK ]%>'
+               });
+       }
+};
+
+var oAPIHosts = {
+       load_api_host_window: function(name) {
+               var title_add = document.getElementById('api_host_window_title_add');
+               var title_edit = document.getElementById('api_host_window_title_edit');
+               var api_host_win_type = document.getElementById('<%=$this->APIHostWindowType->ClientID%>');
+               var host_name = document.getElementById('<%=$this->APIHostName->ClientID%>');
+               // callback is sent both for new and edit api_host because there is realized
+               // checking if password is allowed to set or not
+               var cb = <%=$this->LoadAPIHost->ActiveControl->Javascript%>;
+               cb.setCallbackParameter(name);
+               cb.dispatch();
+               if (name) {
+                       // edit existing api_host
+                       title_add.style.display = 'none';
+                       title_edit.style.display = 'inline-block';
+                       api_host_win_type.value = 'edit';
+                       host_name.setAttribute('readonly', '');
+               } else {
+                       // add new api_host
+                       title_add.style.display = 'inline-block';
+                       title_edit.style.display = 'none';
+                       api_host_win_type.value = 'add';
+                       host_name.removeAttribute('readonly');
+                       this.clear_api_host_window();
+               }
+               document.getElementById('api_host_window').style.display = 'block';
+       },
+       load_api_host_list: function() {
+               var cb = <%=$this->APIHostList->ActiveControl->Javascript%>;
+               cb.dispatch();
+       },
+       load_api_host_list_cb: function(list) {
+               oAPIHostList.data = list;
+               oAPIHostList.init();
+       },
+       clear_api_host_window: function() {
+               [
+                       '<%=$this->APIHostAddress->ClientID%>',
+                       '<%=$this->APIHostPort->ClientID%>',
+                       '<%=$this->APIHostOAuth2ClientId->ClientID%>',
+                       '<%=$this->APIHostOAuth2ClientSecret->ClientID%>',
+                       '<%=$this->APIHostOAuth2RedirectURI->ClientID%>',
+                       '<%=$this->APIHostOAuth2Scope->ClientID%>',
+                       '<%=$this->APIHostName->ClientID%>',
+                       '<%=$this->APIHostBasicLogin->ClientID%>',
+                       '<%=$this->APIHostBasicPassword->ClientID%>'
+
+               ].forEach(function(id) {
+                       document.getElementById(id).value = '';
+               });
+
+               document.getElementById('<%=$this->APIHostProtocol->ClientID%>').value = 'https';
+
+               [
+                       '<%=$this->APIHostTestResultOk->ClientID%>',
+                       '<%=$this->APIHostTestResultErr->ClientID%>',
+                       '<%=$this->APIHostCatalogSupportYes->ClientID%>',
+                       '<%=$this->APIHostCatalogSupportNo->ClientID%>',
+                       '<%=$this->APIHostConsoleSupportYes->ClientID%>',
+                       '<%=$this->APIHostConsoleSupportNo->ClientID%>',
+                       '<%=$this->APIHostConfigSupportYes->ClientID%>',
+                       '<%=$this->APIHostConfigSupportNo->ClientID%>',
+                       '<%=$this->APIHostTestLoader->ClientID%>'
+               ].forEach(function(id) {
+                       document.getElementById(id).style.display= 'none';
+               });
+       },
+       save_api_host_cb: function() {
+               document.getElementById('api_host_window').style.display = 'none';
+       }
+}
+
+$(function() {
+       oAPIHosts.load_api_host_list();
+});
+               </script>
+       </div>
+       <div id="api_host_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('api_host_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                               <h2 id="api_host_window_title_add" style="display: none"><%[ Add API host ]%></h2>
+                               <h2 id="api_host_window_title_edit" style="display: none"><%[ Edit API host ]%></h2>
+                       </header>
+                       <div class="w3-container w3-margin-left w3-margin-right w3-margin-top w3-text-teal">
+                               <div id="api_host_settings">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIHostSettings" Text="<%[ Get existing API host settings: ]%>" /></div>
+                                               <div class="w3-col w3-third">
+                                                       <com:TActiveDropDownList
+                                                               ID="APIHostSettings"
+                                                               CssClass="w3-select w3-border"
+                                                               CausesValidation="false"
+                                                               OnCallback="loadAPIHostSettings"
+                                                       >
+                                                               <prop:ClientSide.OnLoading>
+                                                                       document.getElementById('api_host_settings_loading').style.visibility = 'visible';
+                                                               </prop:ClientSide.OnLoading>
+                                                               <prop:ClientSide.OnComplete>
+                                                                       document.getElementById('api_host_settings_loading').style.visibility = 'hidden';
+                                                               </prop:ClientSide.OnComplete>
+                                                       </com:TActiveDropDownList>
+                                               </div> &nbsp;<i id="api_host_settings_loading" class="fas fa-sync w3-spin w3-text-black" style="visibility: hidden; margin: 10px 5px"></i>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="APIHostProtocol" Text="<%[ Protocol: ]%>" /></div>
+                                       <div class="w3-col w3-third">
+                                               <com:TActiveDropDownList ID="APIHostProtocol" CssClass="w3-select w3-border" Width="150px" CausesValidation="false">
+                                                       <com:TListItem Value="http" Text="HTTP" />
+                                                       <com:TListItem Value="https" Text="HTTPS" Selected="true"/>
+                                               </com:TActiveDropDownList>&nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="APIHostAddress" Text="<%[ IP Address/Hostname: ]%>" /></div>
+                                       <div class="w3-col w3-half">
+                                               <com:TActiveTextBox ID="APIHostAddress" CssClass="w3-input w3-border" CausesValidation="false" />
+                                               <com:TRequiredFieldValidator ValidationGroup="APIHostGroup" CssClass="validator-block" Display="Dynamic" ControlCssClass="invalidate" ControlToValidate="APIHostAddress" Text="<%[ Please enter API address. ]%>" />
+                                       </div>
+                                       &nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="APIHostPort" Text="<%[ Port: ]%>" /></div>
+                                       <div class="w3-col w3-third">
+                                               <com:TActiveTextBox ID="APIHostPort" CssClass="w3-input w3-border" CausesValidation="false" Text="9096" Width="70px" Style="display: inline-block" />
+                                               &nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                                               <com:TRequiredFieldValidator ValidationGroup="APIHostGroup" CssClass="validator-block" Display="Dynamic" ControlCssClass="invalidate" ControlToValidate="APIHostPort" Text="<%[ Please enter API port. ]%>" />
+                                       </div>
+                               </div>
+                               <div class="auth_setting">
+                                       <div class="w3-row w3-section">
+                                               <com:TActiveRadioButton
+                                                       ID="APIHostAuthOAuth2"
+                                                       GroupName="SelectAuth"
+                                                       CssClass="w3-radio"
+                                                       Attributes.onclick="$('#configure_basic_auth').hide();$('#configure_oauth2_auth').show();"
+                                               />
+                                               <com:TLabel
+                                                       ForControl="APIHostAuthOAuth2"
+                                                       CssClass="normal w3-radio"
+                                                       Style="vertical-align: super"
+                                                       Text="<%[ Use OAuth2 for authorization and authentication ]%>"
+                                               />
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <com:TActiveRadioButton
+                                                       ID="APIHostAuthBasic"
+                                                       GroupName="SelectAuth"
+                                                       Checked="true"
+                                                       CssClass="w3-radio"
+                                                       Attributes.onclick="$('#configure_oauth2_auth').hide();$('#configure_basic_auth').show();"
+                                               />
+                                               <com:TLabel
+                                                       ForControl="APIHostAuthBasic"
+                                                       CssClass="normal w3-radio"
+                                                       Style="vertical-align: super"
+                                                       Text="<%[ Use HTTP Basic authentication ]%>"
+                                               />
+                                       </div>
+                               </div>
+                               <div id="configure_basic_auth" style="display: <%=($this->APIHostAuthBasic->Checked === true) ? '' : 'none';%>">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIHostBasicLogin" Text="<%[ API Login: ]%>" /></div>
+                                               <div class="w3-col w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="APIHostBasicLogin"
+                                                               CssClass="w3-input w3-border"
+                                                               CausesValidation="false"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               CssClass="validator-block"
+                                                               Display="Dynamic"
+                                                               ControlCssClass="invalidate"
+                                                               ControlToValidate="APIHostBasicLogin"
+                                                               ValidationGroup="APIHostBasic"
+                                                               Text="<%[ Please enter API login. ]%>"
+                                                        />
+                                               </div>
+                                               &nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIHostBasicPassword" Text="<%[ API Password: ]%>" /></div>
+                                               <div class="w3-col w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="APIHostBasicPassword"
+                                                               TextMode="Password"
+                                                               CssClass="w3-input w3-border"
+                                                               CausesValidation="false"
+                                                               PersistPassword="true"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               CssClass="validator-block"
+                                                               Display="Dynamic"
+                                                               ControlCssClass="invalidate"
+                                                               ControlToValidate="APIHostBasicPassword"
+                                                               ValidationGroup="APIHostBasic"
+                                                               Text="<%[ Please enter API password. ]%>"
+                                                       />
+                                               </div>
+                                               &nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                                       </div>
+                               </div>
+                               <div id="configure_oauth2_auth" style="display: <%=($this->APIHostAuthOAuth2->Checked === true) ? '' : 'none';%>">
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIHostOAuth2ClientSettings" Text="<%[ Get existing OAuth2 client settings: ]%>" /></div>
+                                               <div class="w3-col w3-third">
+                                                       <com:TActiveDropDownList
+                                                               ID="APIHostOAuth2ClientSettings"
+                                                               CssClass="w3-select w3-border"
+                                                               CausesValidation="false"
+                                                               OnCallback="loadOAuth2ClientSettings"
+                                                       >
+                                                               <prop:ClientSide.OnLoading>
+                                                                       document.getElementById('api_host_oauth2_client_settings_loading').style.visibility = 'visible';
+                                                               </prop:ClientSide.OnLoading>
+                                                               <prop:ClientSide.OnComplete>
+                                                                       document.getElementById('api_host_oauth2_client_settings_loading').style.visibility = 'hidden';
+                                                               </prop:ClientSide.OnComplete>
+                                                       </com:TActiveDropDownList>
+                                               </div> &nbsp;<i id="api_host_oauth2_client_settings_loading" class="fas fa-sync w3-spin w3-text-black" style="visibility: hidden; margin: 10px 5px"></i>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIHostOAuth2ClientId" Text="<%[ OAuth2 Client ID: ]%>" /></div>
+                                               <div class="w3-col w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="APIHostOAuth2ClientId"
+                                                               CssClass="w3-input w3-border"
+                                                               CausesValidation="false"
+                                                               MaxLength="32"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               CssClass="validator-block"
+                                                               Display="Dynamic"
+                                                               ControlCssClass="invalidate"
+                                                               ControlToValidate="APIHostOAuth2ClientId"
+                                                               ValidationGroup="APIHostOAuth2"
+                                                               Text="<%[ Please enter Client ID. ]%>"
+                                                       />
+                                                       <com:TRegularExpressionValidator
+                                                               CssClass="validator-block"
+                                                               Display="Dynamic"
+                                                               ControlCssClass="invalidate"
+                                                               ControlToValidate="APIHostOAuth2ClientId"
+                                                               RegularExpression="<%=OAuth2::CLIENT_ID_PATTERN%>"
+                                                               ValidationGroup="APIHostOAuth2"
+                                                               Text="<%[ Invalid Client ID value. Client ID may contain a-z A-Z 0-9 - _ characters. ]%>"
+                                                               />
+                                               </div>
+                                               &nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIHostOAuth2ClientSecret" Text="<%[ OAuth2 Client Secret: ]%>" /></div>
+                                               <div class="w3-col w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="APIHostOAuth2ClientSecret"
+                                                               CssClass="w3-input w3-border"
+                                                               CausesValidation="false"
+                                                               MaxLength="50"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               CssClass="validator-block"
+                                                               Display="Dynamic"
+                                                               ControlCssClass="invalidate"
+                                                               ControlToValidate="APIHostOAuth2ClientSecret"
+                                                               ValidationGroup="APIHostOAuth2"
+                                                               Text="<%[ Please enter Client Secret. ]%>"
+                                                       />
+                                                       <com:TRegularExpressionValidator
+                                                               CssClass="validator-block"
+                                                               Display="Dynamic"
+                                                               ControlCssClass="invalidate"
+                                                               ControlToValidate="APIHostOAuth2ClientSecret"
+                                                               RegularExpression="<%=OAuth2::CLIENT_SECRET_PATTERN%>"
+                                                               ValidationGroup="APIHostOAuth2"
+                                                               Text="<%[ Invalid Client Secret value. Client Secret may contain any character that is not a whitespace character. ]%>"
+                                                       />
+                                               </div>
+                                               &nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIHostOAuth2RedirectURI" Text="<%[ OAuth2 Redirect URI (example: https://baculumgui:9095/web/redirect): ]%>" /></div>
+                                               <div class="w3-col w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="APIHostOAuth2RedirectURI"
+                                                               CssClass="w3-input w3-border"
+                                                               CausesValidation="false"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               CssClass="validator-block"
+                                                               Display="Dynamic"
+                                                               ControlCssClass="invalidate"
+                                                               ControlToValidate="APIHostOAuth2RedirectURI"
+                                                               ValidationGroup="APIHostOAuth2"
+                                                               Text="<%[ Please enter Redirect URI. ]%>"
+                                                       />
+                                               </div>
+                                               &nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="APIHostOAuth2Scope" Text="<%[ OAuth2 scopes (space separated): ]%>" /></div>
+                                               <div class="w3-col w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="APIHostOAuth2Scope"
+                                                               CssClass="w3-input w3-border"
+                                                               CausesValidation="false"
+                                                               TextMode="MultiLine"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               CssClass="validator-block"
+                                                               Display="Dynamic"
+                                                               ControlCssClass="invalidate"
+                                                               ControlToValidate="APIHostOAuth2Scope"
+                                                               ValidationGroup="APIHostOAuth2"
+                                                               Text="<%[ Please enter OAuth2 scopes. ]%>"
+                                                       />
+                                               </div>
+                                               &nbsp;<i class="fa fa-asterisk w3-text-red" style="line-height: 40px"></i>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="APIHostConnectionTest" Text="<%[ API connection test: ]%>" /></div>
+                                       <div class="w3-col w3-half">
+                                               <table border="0" cellpadding="1px" id="new_host_status">
+                                                       <tr>
+                                                               <td align="center" valign="middle">
+                                                                       <com:TActiveLinkButton ID="APIHostConnectionTest" CausesValidation="true" OnCallback="connectionAPITest" CssClass="w3-button w3-green">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       $('#<%=$this->APIHostTestResultOk->ClientID%>').hide();
+                                                                                       $('#<%=$this->APIHostTestResultErr->ClientID%>').hide();
+                                                                                       $('#<%=$this->APIHostCatalogSupportYes->ClientID%>').hide();
+                                                                                       $('#<%=$this->APIHostCatalogSupportNo->ClientID%>').hide();
+                                                                                       $('#<%=$this->APIHostConsoleSupportYes->ClientID%>').hide();
+                                                                                       $('#<%=$this->APIHostConsoleSupportNo->ClientID%>').hide();
+                                                                                       $('#<%=$this->APIHostConfigSupportYes->ClientID%>').hide();
+                                                                                       $('#<%=$this->APIHostConfigSupportNo->ClientID%>').hide();
+                                                                                       $('#<%=$this->APIHostTestLoader->ClientID%>').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#<%=$this->APIHostTestLoader->ClientID%>').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                               <i class="fa fa-play"></i> &nbsp;<%[ test ]%>
+                                                                       </com:TActiveLinkButton>
+                                                               </td>
+                                                               <td valign="middle">
+                                                                       <com:TActiveLabel ID="APIHostTestLoader" Display="None"><i class="fa fa-sync w3-spin w3-text-black"></i></com:TActiveLabel>
+                                                                       <com:TActiveLabel ID="APIHostTestResultOk" Display="None" CssClass="w3-text-green" EnableViewState="false"><i class="fa fa-check"></i> &nbsp;<%[ OK ]%></com:TActiveLabel>
+                                                                       <com:TActiveLabel ID="APIHostTestResultErr" Display="None" CssClass="w3-text-red" EnableViewState="false"><i class="fa fa-times"></i> &nbsp;<%[ Connection error ]%></com:TActiveLabel>
+                                                               </td>
+                                                       </tr>
+                                                       <tr>
+                                                               <td><%[ Catalog support ]%></td>
+                                                               <td>
+                                                                       <com:TActiveLabel ID="APIHostCatalogSupportYes" Display="None" CssClass="w3-text-green" EnableViewState="false"><i class="fa fa-check"></i> &nbsp;<strong><%[ Supported ]%></strong></com:TActiveLabel>
+                                                                       <com:TActiveLabel ID="APIHostCatalogSupportNo" Display="None" CssClass="w3-text-dark-grey" EnableViewState="false"><i class="fa fa-times"></i> &nbsp;<strong><%[ Not supported ]%></strong></com:TActiveLabel>
+                                                               </td>
+                                                       </tr>
+                                                       <tr>
+                                                               <td><%[ Console support ]%></td>
+                                                               <td>
+                                                                       <com:TActiveLabel ID="APIHostConsoleSupportYes" Display="None" CssClass="w3-text-green" EnableViewState="false"><i class="fa fa-check"></i> &nbsp;<strong><%[ Supported ]%></strong></com:TActiveLabel>
+                                                                       <com:TActiveLabel ID="APIHostConsoleSupportNo" Display="None" CssClass="w3-text-dark-grey" EnableViewState="false"><i class="fa fa-times"></i> &nbsp;<strong><%[ Not supported ]%></strong></com:TActiveLabel>
+                                                               </td>
+                                                       </tr>
+                                                       <tr>
+                                                               <td><%[ Config support ]%></td>
+                                                               <td>
+                                                                       <com:TActiveLabel ID="APIHostConfigSupportYes" Display="None" CssClass="w3-text-green" EnableViewState="false"><i class="fa fa-check"></i> &nbsp;<strong><%[ Supported ]%></strong></com:TActiveLabel>
+                                                                       <com:TActiveLabel ID="APIHostConfigSupportNo" Display="None" CssClass="w3-text-dark-grey" EnableViewState="false"><i class="fa fa-times"></i> &nbsp;<strong><%[ Not supported ]%></strong></com:TActiveLabel>
+                                                               </td>
+                                                       </tr>
+                                               </table>
+                                       </div>
+                               </div>
+                               <div class="w3-row w3-section">
+                                       <div class="w3-col w3-third"><com:TLabel ForControl="APIHostName" Text="<%[ Short name: ]%>" /></div>
+                                       <div class="w3-col w3-half">
+                                               <com:TActiveTextBox
+                                                       ID="APIHostName"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                               />
+                                       </div>
+                               </div>
+                       </div>
+                       <footer class="w3-container w3-center">
+                               <button type="button" class="w3-button w3-red" onclick="document.getElementById('api_host_window').style.display = 'none';"><i class="fas fa-times"></i> &nbsp;<%[ Cancel ]%></button>
+                               <com:TActiveLinkButton
+                                       ID="APIHostSave"
+                                       ValidationGroup="APIHostGroup"
+                                       CausesValidation="true"
+                                       OnCallback="saveAPIHost"
+                                       CssClass="w3-button w3-section w3-teal w3-padding"
+                                       Attributes.onclick="return fields_validation()"
+                               >
+                                       <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
+                               </com:TActiveLinkButton>
+                       </footer>
+               </div>
+<script type="text/javascript">
+       var fields_validation = function() {
+               var basic = document.getElementById('<%=$this->APIHostAuthBasic->ClientID%>');
+               var oauth2 = document.getElementById('<%=$this->APIHostAuthOAuth2->ClientID%>');
+               var validation_group;
+               if (basic.checked === true) {
+                       validation_group = 'APIHostBasic';
+               } else if (oauth2.checked === true) {
+                       validation_group = 'APIHostOAuth2';
+               }
+               return Prado.Validation.validate(Prado.Validation.getForm(), validation_group);
+       }
+</script>
+               <com:TActiveHiddenField ID="APIHostWindowType" />
+       </div>
 </com:TContent>
index a9d8decff965f27cdbcfa3a1ee4648ecc5c57d42..23983e96854ed8bb4bc4fe2c810a68c6c5d12dfb 100644 (file)
@@ -27,6 +27,7 @@ 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.TActiveListBox');
+Prado::using('System.Web.UI.ActiveControls.TActiveRadioButton');
 Prado::using('System.Web.UI.ActiveControls.TActiveTextBox');
 Prado::using('System.Web.UI.ActiveControls.TCallback');
 Prado::using('System.Web.UI.WebControls.TCheckBox');
@@ -38,7 +39,9 @@ Prado::using('System.Web.UI.WebControls.TRequiredFieldValidator');
 Prado::using('System.Web.UI.WebControls.TValidationSummary');
 Prado::using('Application.Common.Class.Crypto');
 Prado::using('Application.Common.Class.Ldap');
+Prado::using('Application.Common.Class.OAuth2');
 Prado::using('Application.Web.Class.BaculumWebPage');
+Prado::using('Application.Web.Portlets.BaculaConfigResources');
 
 /**
  * Security page (auth methods, users, roles...).
@@ -76,6 +79,11 @@ class Security extends BaculumWebPage {
         */
        private $user_config = [];
 
+       /**
+        * Store console ACL config.
+        */
+       private $console_config = [];
+
        /**
         * Initialize page.
         *
@@ -1170,6 +1178,546 @@ class Security extends BaculumWebPage {
                return (($is_basic && $allow_manage_users) || $is_local);
        }
 
+       /**
+        * Set and load console ACL list.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter callback parameter
+        * @return none
+        */
+       public function setConsoleList($sender, $param) {
+               $config = $this->getModule('api')->get(['config', 'dir', 'Console']);
+               $console_directives = [
+                       'Description' => '',
+                       'JobAcl' => '',
+                       'ClientAcl' => '',
+                       'StorageAcl' => '',
+                       'ScheduleAcl' => '',
+                       'RunAcl' => '',
+                       'PoolAcl' => '',
+                       'CommandAcl' => '',
+                       'FilesetAcl' => '',
+                       'CatalogAcl' => '',
+                       'WhereAcl' => '',
+                       'PluginOptionsAcl' => '',
+                       'BackupClientAcl' => '',
+                       'RestoreClientAcl' => '',
+                       'DirectoryAcl' => ''
+               ];
+               $consoles = [];
+               function join_cons($item) {
+                       if (is_array($item)) {
+                               $item = implode(',', $item);
+                       }
+                       return $item;
+               }
+               if ($config->error == 0) {
+                       for ($i = 0; $i < count($config->output); $i++) {
+                               $cons = (array)$config->output[$i]->Console;
+                               $cons = array_map('join_cons', $cons);
+                               $consoles[] = array_merge($console_directives, $cons);
+                       }
+               }
+               $this->getCallbackClient()->callClientFunction('oConsoles.load_console_list_cb', [
+                       $consoles
+               ]);
+               $this->console_config = $consoles;
+       }
+
+       /**
+        * Load data in console modal window.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function loadConsoleWindow($sender, $param) {
+               $name = $param->getCallbackParameter();
+               if (!empty($name)) {
+                       // edit existing console
+                       $this->ConsoleConfig->setResourceName($name);
+                       $this->ConsoleConfig->setLoadValues(true);
+               } else {
+                       // add new console
+                       $this->ConsoleConfig->setLoadValues(false);
+                       $this->getCallbackClient()->callClientFunction('oBaculaConfigSection.show_sections', [true]);
+               }
+               $this->ConsoleConfig->setHost($this->User->getAPIHosts());
+               $this->ConsoleConfig->setComponentName($_SESSION['dir']);
+               $this->ConsoleConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+       }
+
+       /**
+        * Remove consoles action.
+        * Here is possible to remove one console or many.
+        * This action is linked with table bulk actions.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function removeConsoles($sender, $param) {
+               $consoles = explode('|', $param->getCallbackParameter());
+               $res = new BaculaConfigResources();
+               $config = $res->getConfigData($this->User->getAPIHosts(), 'dir');
+               for ($i = 0; $i < count($consoles); $i++) {
+                       $res->removeResourceFromConfig(
+                               $config,
+                               'Console',
+                               $consoles[$i]
+                       );
+               }
+               $this->getModule('api')->set(
+                       array('config', 'dir'),
+                       array('config' => json_encode($config)),
+                       $this->User->getAPIHosts(),
+                       false
+               );
+
+               // refresh console list
+               $this->setConsoleList(null, null);
+
+               // refresh OAuth2 client console combobox
+               $this->loadOAuth2ClientConsole(null, null);
+       }
+
+       public function setAllCommandAcls($sender, $param) {
+               $config = (object)[
+                       "CommandAcl" => [
+                               'gui',
+                               '.api',
+                               '.jobs',
+                               '.ls',
+                               '.client',
+                               '.fileset',
+                               '.pool',
+                               '.status',
+                               '.storage',
+                               '.bvfs_get_jobids',
+                               '.bvfs_update',
+                               '.bvfs_lsdirs',
+                               '.bvfs_lsfiles',
+                               '.bvfs_versions',
+                               '.bvfs_restore',
+                               '.bvfs_cleanup',
+                               'restore',
+                               'show',
+                               'estimate',
+                               'run',
+                               'delete',
+                               'cancel'
+                       ]
+               ];
+               $this->ConsoleConfig->setData($config);
+               $this->ConsoleConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+               $this->getCallbackClient()->callClientFunction('oBaculaConfigSection.show_sections', [true]);
+       }
+
+       /**
+        * Set and load OAuth2 client list.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter callback parameter
+        * @return none
+        */
+       public function setOAuth2ClientList($sender, $param) {
+               $oauth2_clients = $this->getModule('api')->get(['oauth2', 'clients']);
+               $this->getCallbackClient()->callClientFunction('oOAuth2Clients.load_oauth2_client_list_cb', [
+                       $oauth2_clients->output,
+                       $oauth2_clients->error
+               ]);
+       }
+
+       public function loadOAuth2ClientConsole($sender, $param) {
+               $cons = $this->getModule('api')->get(['config', 'dir', 'Console']);
+               $console = ['' => ''];
+               if ($cons->error == 0) {
+                       for ($i = 0; $i < count($cons->output); $i++) {
+                               $console[$cons->output[$i]->Console->Name] = $cons->output[$i]->Console->Name;
+                       }
+               }
+               $this->OAuth2ClientConsole->DataSource = $console;
+               $this->OAuth2ClientConsole->dataBind();
+       }
+
+       /**
+        * Load data in OAuth2 client modal window.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function loadOAuth2ClientWindow($sender, $param) {
+               $client_id = $param->getCallbackParameter();
+               if (!empty($client_id)) {
+                       $oauth2_cfg = $this->getModule('api')->get(['oauth2', 'clients', $client_id]);
+                       if ($oauth2_cfg->error === 0 && is_object($oauth2_cfg->output)) {
+                               // It is done only for existing OAuth2 client accounts
+                               $this->OAuth2ClientClientId->Text = $oauth2_cfg->output->client_id;
+                               $this->OAuth2ClientClientSecret->Text = $oauth2_cfg->output->client_secret;
+                               $this->OAuth2ClientRedirectURI->Text = $oauth2_cfg->output->redirect_uri;
+                               $this->OAuth2ClientScope->Text = $oauth2_cfg->output->scope;
+                               $this->OAuth2ClientBconsoleCfgPath->Text = $oauth2_cfg->output->bconsole_cfg_path;
+                               $this->OAuth2ClientName->Text = $oauth2_cfg->output->name;
+                       }
+               }
+               $this->loadOAuth2ClientConsole(null, null);
+
+               $dirs = $this->getModule('api')->get(['config', 'bcons', 'Director']);
+               $dir_names = [];
+               if ($dirs->error == 0) {
+                       for ($i = 0; $i < count($dirs->output); $i++) {
+                               $dir_names[$dirs->output[$i]->Director->Name] = $dirs->output[$i]->Director->Name;
+                       }
+               }
+               $this->OAuth2ClientDirector->DataSource = $dir_names;
+               $this->OAuth2ClientDirector->dataBind();
+       }
+
+       /**
+        * Save OAuth2 client.
+        * It works both for new OAuth2 client and for edited OAuth2 client.
+        * Saves values from modal popup.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function saveOAuth2Client($sender, $param) {
+               $client_id = $this->OAuth2ClientClientId->Text;
+               $cfg = [];
+               $cfg['client_id'] = $client_id;
+               $cfg['client_secret'] = $this->OAuth2ClientClientSecret->Text;
+               $cfg['redirect_uri'] = $this->OAuth2ClientRedirectURI->Text;
+               $cfg['scope'] = $this->OAuth2ClientScope->Text;
+               $cfg['bconsole_cfg_path'] = $this->OAuth2ClientBconsoleCfgPath->Text;
+               if ($this->OAuth2ClientBconsoleCreate->Checked) {
+                       $cfg['console'] = $this->OAuth2ClientConsole->SelectedValue;
+                       $cfg['director'] = $this->OAuth2ClientDirector->SelectedValue;
+               }
+               $cfg['name'] = $this->OAuth2ClientName->Text;
+
+               $win_type = $this->OAuth2ClientWindowType->Value;
+               $result = (object)['error' => -1];
+               if ($win_type === self::TYPE_ADD_WINDOW) {
+                       $result = $this->getModule('api')->create(['oauth2', 'clients', $client_id], $cfg);
+               } elseif ($win_type === self::TYPE_EDIT_WINDOW) {
+                       $result = $this->getModule('api')->set(['oauth2', 'clients', $client_id], $cfg);
+               }
+
+               if ($result->error === 0) {
+                       // Refresh OAuth2 client list
+                       $this->setOAuth2ClientList(null, null);
+               }
+               $this->getCallbackClient()->callClientFunction('oOAuth2Clients.save_oauth2_client_cb', [
+                       $result
+               ]);
+       }
+
+       /**
+        * Remove OAuth2 client action.
+        * Here is possible to remove one OAuth2 client or many.
+        * This action is linked with table bulk actions.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function removeOAuth2Clients($sender, $param) {
+               $client_ids = explode('|', $param->getCallbackParameter());
+               for ($i = 0; $i < count($client_ids); $i++) {
+                       $result = $this->getModule('api')->remove(['oauth2', 'clients', $client_ids[$i]]);
+                       if ($result->error !== 0) {
+                               break;
+                       }
+               }
+
+               if (count($client_ids) > 0) {
+                       // Refresh OAuth2 client list
+                       $this->setOAuth2ClientList(null, null);
+               }
+       }
+
+       /**
+        * Set and load OAuth2 client settings to API host modal window.
+        *
+        * @param TActiveDropDownList $sender sender object
+        * @param TCallbackEventParameter callback parameter
+        * @return none
+        */
+       public function loadOAuth2ClientSettings($sender, $param) {
+               $client_id = $this->APIHostOAuth2ClientSettings->SelectedValue;
+               if (!empty($client_id)) {
+                       $host = $this->APIHostSettings->SelectedValue ?: null;
+                       $oauth2_cfg = $this->getModule('api')->get(['oauth2', 'clients', $client_id], $host);
+                       if ($oauth2_cfg->error === 0 && is_object($oauth2_cfg->output)) {
+                               $this->APIHostOAuth2ClientId->Text = $oauth2_cfg->output->client_id;
+                               $this->APIHostOAuth2ClientSecret->Text = $oauth2_cfg->output->client_secret;
+                               $this->APIHostOAuth2RedirectURI->Text = $oauth2_cfg->output->redirect_uri;
+                               $this->APIHostOAuth2Scope->Text = $oauth2_cfg->output->scope;
+                       }
+               }
+       }
+
+       /**
+        * Load OAuth2 client list to get OAuth2 client settings.
+        *
+        * @return none
+        */
+       private function loadOAuth2ClientList() {
+               $host = $this->APIHostSettings->SelectedValue ?: null;
+               $oauth2_clients = $this->getModule('api')->get(['oauth2', 'clients'], $host);
+               $oauth2_client_list = ['' => ''];
+               if ($oauth2_clients->error == 0 && is_array($oauth2_clients->output)) {
+                       for ($i = 0; $i < count($oauth2_clients->output); $i++) {
+                               $name = $oauth2_clients->output[$i]->name ?: $oauth2_clients->output[$i]->client_id;
+                               $oauth2_client_list[$oauth2_clients->output[$i]->client_id] = $name;
+                       }
+               }
+               $this->APIHostOAuth2ClientSettings->DataSource = $oauth2_client_list;
+               $this->APIHostOAuth2ClientSettings->dataBind();
+       }
+
+       /**
+        * Set and load API hosts list.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter callback parameter
+        * @return none
+        */
+       public function setAPIHostList($sender, $param) {
+               $api_hosts = $this->getModule('host_config')->getConfig();
+               $shortnames = array_keys($api_hosts);
+               $attributes = array_values($api_hosts);
+               for ($i = 0; $i < count($attributes); $i++) {
+                       $attributes[$i]['name'] = $shortnames[$i];
+               }
+
+               $this->getCallbackClient()->callClientFunction('oAPIHosts.load_api_host_list_cb', [
+                       $attributes
+               ]);
+       }
+
+       /**
+        * Load data in API host modal window.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function loadAPIHostWindow($sender, $param) {
+               $name = $param->getCallbackParameter();
+
+               // prepare API host combobox
+               $api_hosts = $this->getModule('host_config')->getConfig();
+
+               if (!empty($name) && key_exists($name, $api_hosts)) {
+                       $this->APIHostAddress->Text = $api_hosts[$name]['address'];
+                       $this->APIHostProtocol->SelectedValue = $api_hosts[$name]['protocol'];
+                       $this->APIHostPort->Text = $api_hosts[$name]['port'];
+                       $this->APIHostOAuth2ClientId->Text = $api_hosts[$name]['client_id'];
+                       $this->APIHostOAuth2ClientSecret->Text = $api_hosts[$name]['client_secret'];
+                       $this->APIHostOAuth2RedirectURI->Text = $api_hosts[$name]['redirect_uri'];
+                       $this->APIHostOAuth2Scope->Text = $api_hosts[$name]['scope'];
+                       $this->APIHostName->Text = $name;
+                       $this->APIHostBasicLogin->Text = $api_hosts[$name]['login'];
+                       $this->APIHostBasicPassword->Text = $api_hosts[$name]['password'];
+                       if ($api_hosts[$name]['auth_type'] == 'basic') {
+                               $this->APIHostAuthBasic->Checked = true;
+                               $this->getCallbackClient()->hide('configure_oauth2_auth');
+                               $this->getCallbackClient()->show('configure_basic_auth');
+                       } elseif ($api_hosts[$name]['auth_type'] == 'oauth2') {
+                               $this->APIHostAuthOAuth2->Checked = true;
+                               $this->getCallbackClient()->hide('configure_basic_auth');
+                               $this->getCallbackClient()->show('configure_oauth2_auth');
+                       }
+               }
+
+               $shortnames = array_keys($api_hosts);
+
+               $api_host_names = array_combine($shortnames, $shortnames);
+               $this->APIHostSettings->DataSource = array_merge(['' => ''], $api_host_names);
+               $this->APIHostSettings->dataBind();
+
+               // prepare OAuth2 client combobox
+               $this->loadOAuth2ClientList();
+       }
+
+       /**
+        * Load API host settings to API host modal window.
+        *
+        * @param TActiveDropDownList $sender sender object
+        * @param TCallbackEventParameter callback parameter
+        * @return none
+        */
+       public function loadAPIHostSettings($sender, $param) {
+               $api_host = $this->APIHostSettings->SelectedValue;
+               if (!empty($api_host)) {
+                       $config = $this->getModule('host_config')->getConfig();
+                       if (key_exists($api_host, $config)) {
+                               // load OAuth2 clients to combobox from selected API host
+                               $this->loadOAuth2ClientList();
+
+                               $this->APIHostProtocol->SelectedValue = $config[$api_host]['protocol'];
+                               $this->APIHostAddress->Text = $config[$api_host]['address'];
+                               $this->APIHostPort->Text = $config[$api_host]['port'];
+                               if ($config[$api_host]['auth_type'] == 'basic') {
+                                       $this->APIHostAuthBasic->Checked = true;
+                                       $this->getCallbackClient()->hide('configure_oauth2_auth');
+                                       $this->getCallbackClient()->show('configure_basic_auth');
+                               } elseif ($config[$api_host]['auth_type'] == 'oauth2') {
+                                       $this->APIHostAuthOAuth2->Checked = true;
+                                       $this->getCallbackClient()->hide('configure_basic_auth');
+                                       $this->getCallbackClient()->show('configure_oauth2_auth');
+                               }
+                       }
+               }
+       }
+
+       public function connectionAPITest($sender, $param) {
+               $host = $this->APIHostAddress->Text;
+               if (empty($host)) {
+                       $host = false;
+               }
+               $host_params = array(
+                       'protocol' => $this->APIHostProtocol->SelectedValue,
+                       'address' => $this->APIHostAddress->Text,
+                       'port' => $this->APIHostPort->Text,
+                       'url_prefix' => ''
+               );
+
+               if ($this->APIHostAuthBasic->Checked) {
+                       $host_params['auth_type'] = 'basic';
+                       $host_params['login'] = $this->APIHostBasicLogin->Text;
+                       $host_params['password'] = $this->APIHostBasicPassword->Text;
+               } elseif ($this->APIHostAuthOAuth2->Checked) {
+                       $host_params['auth_type'] = 'oauth2';
+                       $host_params['client_id'] = $this->APIHostOAuth2ClientId->Text;
+                       $host_params['client_secret'] = $this->APIHostOAuth2ClientSecret->Text;
+                       $host_params['redirect_uri'] = $this->APIHostOAuth2RedirectURI->Text;
+                       $host_params['scope'] = $this->APIHostOAuth2Scope->Text;
+               }
+               $api = $this->getModule('api');
+
+               // Catalog test
+               OAuth2Record::deleteByPk($host);
+               $api->setHostParams($host, $host_params);
+               $catalog = $api->get(array('catalog'), $host, false);
+
+               // Console test
+               OAuth2Record::deleteByPk($host);
+               $api->setHostParams($host, $host_params);
+               $director = null;
+               if (array_key_exists('director', $_SESSION)) {
+                       // Current director can't be passed to new remote host.
+                       $director = $_SESSION['director'];
+                       unset($_SESSION['director']);
+               }
+
+               $console = $api->set(array('console'), array('version'), $host, false);
+               if (!is_null($director)) {
+                       // Revert director setting if any
+                       $_SESSION['director'] = $director;
+               }
+
+               // Config test
+               OAuth2Record::deleteByPk($host);
+               $api->setHostParams($host, $host_params);
+               $config = $api->get(array('config'), $host, false);
+
+               $is_catalog = (is_object($catalog) && $catalog->error === 0);
+               $is_console = (is_object($console) && $console->error === 0);
+               $is_config = (is_object($config) && $config->error === 0);
+
+               $status_ok = $is_catalog;
+               if ($status_ok) {
+                       $status_ok = $is_console;
+               }
+
+               if (!$is_catalog) {
+                       $this->APIHostTestResultErr->Text .= $catalog->output . '<br />';
+               }
+               if (!$is_console) {
+                       $this->APIHostTestResultErr->Text .= $console->output . '<br />';
+               }
+               if (!$is_config) {
+                       $this->APIHostTestResultErr->Text .= $config->output . '<br />';
+               }
+
+               $this->APIHostTestResultOk->Display = ($status_ok === true) ? 'Dynamic' : 'None';
+               $this->APIHostTestResultErr->Display = ($status_ok === false) ? 'Dynamic' : 'None';
+               $this->APIHostCatalogSupportYes->Display = ($is_catalog === true) ? 'Dynamic' : 'None';
+               $this->APIHostCatalogSupportNo->Display = ($is_catalog === false) ? 'Dynamic' : 'None';
+               $this->APIHostConsoleSupportYes->Display = ($is_console === true) ? 'Dynamic' : 'None';
+               $this->APIHostConsoleSupportNo->Display = ($is_console === false) ? 'Dynamic' : 'None';
+               $this->APIHostConfigSupportYes->Display = ($is_config === true) ? 'Dynamic' : 'None';
+               $this->APIHostConfigSupportNo->Display = ($is_config === false) ? 'Dynamic' : 'None';
+       }
+
+       public function saveAPIHost($sender, $param) {
+               $cfg_host = array(
+                       'auth_type' => '',
+                       'login' => '',
+                       'password' => '',
+                       'client_id' => '',
+                       'client_secret' => '',
+                       'redirect_uri' => '',
+                       'scope' => ''
+               );
+               $cfg_host['protocol'] = $this->APIHostProtocol->Text;
+               $cfg_host['address'] = $this->APIHostAddress->Text;
+               $cfg_host['port'] = $this->APIHostPort->Text;
+               $cfg_host['url_prefix'] = '';
+               if ($this->APIHostAuthBasic->Checked == true) {
+                       $cfg_host['auth_type'] = 'basic';
+                       $cfg_host['login'] = $this->APIHostBasicLogin->Text;
+                       $cfg_host['password'] = $this->APIHostBasicPassword->Text;
+               } elseif($this->APIHostAuthOAuth2->Checked == true) {
+                       $cfg_host['auth_type'] = 'oauth2';
+                       $cfg_host['client_id'] = $this->APIHostOAuth2ClientId->Text;
+                       $cfg_host['client_secret'] = $this->APIHostOAuth2ClientSecret->Text;
+                       $cfg_host['redirect_uri'] = $this->APIHostOAuth2RedirectURI->Text;
+                       $cfg_host['scope'] = $this->APIHostOAuth2Scope->Text;
+               }
+               $hc = $this->getModule('host_config');
+               $config = $hc->getConfig();
+               $host_name = trim($this->APIHostName->Text);
+               if (empty($host_name)) {
+                       $host_name = $cfg_host['address'];
+               }
+               $config[$host_name] = $cfg_host;
+               $hc->setConfig($config);
+               $this->setAPIHostList(null, null);
+               $this->getCallbackClient()->hide('api_host_window');
+
+               // refresh user window
+               $this->initUserWindow();
+       }
+
+       /**
+        * Remove API host action.
+        * Here is possible to remove one API host or many.
+        * This action is linked with table bulk actions.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function removeAPIHosts($sender, $param) {
+               $names = explode('|', $param->getCallbackParameter());
+               $hc = $this->getModule('host_config');
+               $config = $hc->getConfig();
+               $cfg = [];
+               foreach ($config as $host => $opts) {
+                       if (in_array($host, $names)) {
+                               continue;
+                       }
+                       $cfg[$host] = $opts;
+               }
+               $hc->setConfig($cfg);
+               $this->setAPIHostList(null, null);
+       }
+
        /**
         * Validate IP restriction address value.
         *
index 3a52146865bdfa96a2477ddde339e11a5e557b34..39e118f3ee9fd9cf8d6d8ee36071ea6bf299077a 100644 (file)
@@ -51,6 +51,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate {
        const SHOW_REMOVE_BUTTON = 'ShowRemoveButton';
        const SHOW_CANCEL_BUTTON = 'ShowCancelButton';
        const SHOW_ALL_DIRECTIVES = 'ShowAllDirectives';
+       const SHOW_BOTTOM_BUTTONS = 'ShowBottomButtons';
        const SAVE_DIRECTIVE_ACTION_OK = 'SaveDirectiveActionOk';
 
        private $show_all_directives = false;
@@ -122,6 +123,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate {
                $directives = array();
                $parent_directives = array();
                $config = new stdClass;
+               $predefined = false;
                if ($load_values === true) {
                        $config = $this->getConfigData($host, array(
                                $component_type,
@@ -139,6 +141,13 @@ class BaculaConfigDirectives extends DirectiveListTemplate {
                                        $config->JobDefs
                                ));
                        }
+               } else {
+                       // Pre-defined config for new resource can be provided in Data property.
+                       $data = $this->getData();
+                       if (!empty($data)) {
+                               $config = $data;
+                               $predefined = true;
+                       }
                }
 
                $data_desc = $this->Application->getModule('data_desc');
@@ -150,7 +159,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate {
                        }
 
                        $directive_value = null;
-                       if ($in_config === true && $load_values === true) {
+                       if (($in_config === true && $load_values === true) || ($predefined && property_exists($config, $directive_name))) {
                                $directive_value = $config->{$directive_name};
                        }
 
@@ -163,7 +172,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate {
                        if (is_object($directive_desc)) {
                                if (property_exists($directive_desc, 'Required')) {
                                        $required = $directive_desc->Required;
-                                       if ($load_values === true && array_key_exists($directive_name, $parent_directives)) {
+                                       if ($load_values === true && key_exists($directive_name, $parent_directives)) {
                                                // values can be taken from JobDefs
                                                $required = false;
                                        }
@@ -377,6 +386,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate {
                        $this->SaveDirectiveErrMsg->Display = 'Dynamic';
                        $this->SaveDirectiveErrMsg->Text = "Error {$result->error}: {$result->output}";
                }
+               $this->onSave(null);
        }
 
        public function setShowAllDirectives($show_all_directives) {
@@ -574,5 +584,28 @@ class BaculaConfigDirectives extends DirectiveListTemplate {
        public function getShowCancelButton() {
                return $this->getViewState(self::SHOW_CANCEL_BUTTON, true);
        }
+
+       /**
+        * Set if buttons should be flexible and available at the bottom of the page.
+        *
+        * @return none;
+        */
+       public function setShowBottomButtons($show) {
+               $show = TPropertyValue::ensureBoolean($show);
+               $this->setViewState(self::SHOW_BOTTOM_BUTTONS, $show);
+       }
+
+       /**
+        * Get if buttons should be flexible and available at the bottom of the page.
+        *
+        * @return bool true if buttons are available at the bottom of the page, otherwise false
+        */
+       public function getShowBottomButtons() {
+               return $this->getViewState(self::SHOW_BOTTOM_BUTTONS, true);
+       }
+
+       public function onSave($param) {
+               $this->raiseEvent('OnSave', $this, $param);
+       }
 }
 ?>
index e5a89d645ab775cc91fc09d4e1487e91741ecd1f..f54ee41f5f257d4fa7a5e9295333066995ee5357 100644 (file)
@@ -37,7 +37,7 @@
                ItemRenderer="Application.Web.Portlets.DirectiveRenderer"
                >
        </com:TActiveRepeater>
-       <div class="w3-row w3-center w3-border bottom_buttons page_main_el" style="margin-left: 250px">
+       <div class="w3-row w3-center<%=$this->ShowBottomButtons ? ' w3-border bottom_buttons' : ''%> page_main_el"<%=$this->ShowBottomButtons ? ' style="margin-left: 250px"' : ''%>>
                <com:Application.Web.Portlets.DirectiveSetting
                        ID="DirectiveSetting"
                        Resource="<%=$this->getResource()%>"
index 91412cba10f58de2f0ba561b27aa9fb0b5b4a33c..9059d224432192b8e82593b238e421a8dbbf8388 100644 (file)
@@ -37,7 +37,7 @@ class BaculaConfigResources extends ResourceListTemplate {
 
        public $resource_names = array();
 
-       private function getConfigData($host, $component_type) {
+       public function getConfigData($host, $component_type) {
                $params = array('config', $component_type);
                $result = $this->Application->getModule('api')->get($params, $host, false);
                $config = array();
@@ -222,7 +222,7 @@ class BaculaConfigResources extends ResourceListTemplate {
         * @param string $resource_name resource name to remove
         * @return none
         */
-       private function removeResourceFromConfig(&$config, $resource_type, $resource_name) {
+       public function removeResourceFromConfig(&$config, $resource_type, $resource_name) {
                for ($i = 0; $i < count($config); $i++) {
                        foreach ($config[$i] as $rtype => $resource) {
                                if (!property_exists($resource, 'Name')) {
@@ -231,7 +231,7 @@ class BaculaConfigResources extends ResourceListTemplate {
                                if ($rtype === $resource_type && $resource->Name === $resource_name) {
                                        // remove resource
                                        array_splice($config, $i, 1);
-                                       break;
+                                       break 2;
                                }
                        }
                }