]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Implement component start/stop/restart actions in API
authorMarcin Haba <marcin.haba@bacula.pl>
Thu, 1 Aug 2019 04:47:30 +0000 (06:47 +0200)
committerMarcin Haba <marcin.haba@bacula.pl>
Sat, 14 Dec 2019 14:55:27 +0000 (15:55 +0100)
18 files changed:
gui/baculum/protected/API/Class/APIConfig.php
gui/baculum/protected/API/Class/BaculumAPIServer.php
gui/baculum/protected/API/Class/ComponentActions.php [new file with mode: 0644]
gui/baculum/protected/API/JavaScript/misc.js
gui/baculum/protected/API/Lang/en/messages.mo
gui/baculum/protected/API/Lang/en/messages.po
gui/baculum/protected/API/Lang/pl/messages.mo
gui/baculum/protected/API/Lang/pl/messages.po
gui/baculum/protected/API/Lang/pt/messages.mo
gui/baculum/protected/API/Lang/pt/messages.po
gui/baculum/protected/API/Pages/API/Actions.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/Panel/APIInstallWizard.page
gui/baculum/protected/API/Pages/Panel/APIInstallWizard.php
gui/baculum/protected/API/Pages/config.xml
gui/baculum/protected/API/endpoints.xml
gui/baculum/protected/Common/Class/Errors.php
gui/baculum/protected/Common/Class/Miscellaneous.php
gui/baculum/themes/Baculum-v1/css/style.css

index 9c51e0a34b5603119b880f67a54b5f0ea2c12a82..ddcd7fda47b6d6b52f7ea836990a23294e71c367 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2016 Kern Sibbald
+ * Copyright (C) 2013-2019 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -53,6 +53,19 @@ class APIConfig extends ConfigFileModule {
        const JSON_TOOL_FD_TYPE = 'fd';
        const JSON_TOOL_BCONS_TYPE = 'bcons';
 
+       /**
+        * Action types
+        */
+       const ACTION_DIR_START = 'dir_start';
+       const ACTION_DIR_STOP = 'dir_stop';
+       const ACTION_DIR_RESTART = 'dir_restart';
+       const ACTION_SD_START = 'sd_start';
+       const ACTION_SD_STOP = 'sd_stop';
+       const ACTION_SD_RESTART = 'sd_restart';
+       const ACTION_FD_START = 'fd_start';
+       const ACTION_FD_STOP = 'fd_stop';
+       const ACTION_FD_RESTART = 'fd_restart';
+
        /**
         * These options are obligatory for API config.
         */
@@ -107,18 +120,16 @@ class APIConfig extends ConfigFileModule {
         * @return boolean true if config valid, otherwise false
         */
        private function validateConfig(array $config = array()) {
-               $is_valid = $this->isConfigValid($this->required_options, $config, self::CONFIG_FILE_FORMAT, self::CONFIG_FILE_PATH);
-               return $is_valid;
+               return $this->isConfigValid($this->required_options, $config, self::CONFIG_FILE_FORMAT, self::CONFIG_FILE_PATH);
        }
 
        private function getJSONToolTypes() {
-               $types = array(
+               return array(
                        self::JSON_TOOL_DIR_TYPE,
                        self::JSON_TOOL_SD_TYPE,
                        self::JSON_TOOL_FD_TYPE,
                        self::JSON_TOOL_BCONS_TYPE
                );
-               return $types;
        }
 
        /**
@@ -268,4 +279,105 @@ class APIConfig extends ConfigFileModule {
                }
                return $tools;
        }
+
+       /**
+        * Get action types.
+        *
+        * @return array action types
+        */
+       public function getActionTypes() {
+               return array(
+                       self::ACTION_DIR_START,
+                       self::ACTION_DIR_STOP,
+                       self::ACTION_DIR_RESTART,
+                       self::ACTION_SD_START,
+                       self::ACTION_SD_STOP,
+                       self::ACTION_SD_RESTART,
+                       self::ACTION_FD_START,
+                       self::ACTION_FD_STOP,
+                       self::ACTION_FD_RESTART
+               );
+       }
+
+       /**
+        * Check if Actions are configured for application.
+        *
+        * @return boolean true if Actions are configured, otherwise false
+        */
+       public function isActionsConfigured() {
+               $config = $this->getConfig();
+               return key_exists('actions', $config);
+       }
+
+       /**
+        * Check if single action is configured for application.
+        *
+        * @return boolean true if single action is configured, otherwise false
+        */
+       public function isActionConfigured($action_type) {
+               $configured = false;
+               $config = $this->getActionsConfig();
+               return (key_exists($action_type, $config) && !empty($config[$action_type]));
+       }
+
+       /**
+        * Check if Actions support is enabled.
+        *
+        * @return boolean true if Actions support is enabled, otherwise false
+        */
+       public function isActionsEnabled() {
+               $enabled = false;
+               if ($this->isActionsConfigured() === true) {
+                       $config = $this->getConfig();
+                       $enabled = ($config['actions']['enabled'] == 1);
+               }
+               return $enabled;
+       }
+
+       /**
+        * Get Actions config parameters.
+        *
+        * @return array Actions config parameters
+        */
+       public function getActionsConfig() {
+               $cfg = array();
+               if ($this->isActionsConfigured() === true) {
+                       $config = $this->getConfig();
+                       $cfg = $config['actions'];
+               }
+               return $cfg;
+       }
+
+       /**
+        * Get single action command and sudo option.
+        *
+        * @param string $action_type action type (dir_start, dir_stop ...etc.)
+        * @return array command and sudo option state
+        */
+       public function getActionConfig($action_type) {
+               $action = array('cmd' => '', 'use_sudo' => false);
+               $actions = $this->getSupportedActions();
+               $config = $this->getActionsConfig();
+               if (in_array($action_type, $actions) && $this->isActionConfigured($action_type) === true) {
+                       $action['cmd'] = $config[$action_type];
+                       $action['use_sudo'] = ($config['use_sudo'] == 1);
+               }
+               return $action;
+       }
+
+       /**
+        * Get supported actions defined in API config.
+        *
+        * @return array supported actions
+        */
+       public function getSupportedActions() {
+               $actions = array();
+               $types = $this->getActionTypes();
+               for ($i = 0; $i < count($types); $i++) {
+                       if ($this->isActionConfigured($types[$i]) === true) {
+                               array_push($actions, $types[$i]);
+                       }
+               }
+               return $actions;
+       }
 }
index 29c839c98c63065c75a5eb6e8dedb2ef78d63d5e..90e7334cb602434fbb5c835256ecb85da656f78b 100644 (file)
@@ -139,8 +139,8 @@ abstract class BaculumAPIServer extends TPage {
                if ($is_auth === false) {
                        // Authorization error.
                        header(OAuth2::HEADER_UNAUTHORIZED);
-                       $this->output = AuthorizationError::MSG_ERROR_AUTHORIZATION_TO_API_PROBLEM;
-                       $this->error = AuthorizationError::ERROR_AUTHORIZATION_TO_API_PROBLEM;
+                       $this->output = AuthorizationError::MSG_ERROR_AUTHENTICATION_TO_API_PROBLEM;
+                       $this->error = AuthorizationError::ERROR_AUTHENTICATION_TO_API_PROBLEM;
                        return;
                }
                try {
diff --git a/gui/baculum/protected/API/Class/ComponentActions.php b/gui/baculum/protected/API/Class/ComponentActions.php
new file mode 100644 (file)
index 0000000..baaf0d5
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2019 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.API.Class.APIModule');
+Prado::using('Application.Common.Class.Errors');
+
+/**
+ * Module responsible for executing action commands.
+ */
+class ComponentActions extends APIModule {
+
+       /**
+        * Sudo command.
+        */
+       const SUDO = 'sudo';
+
+       /**
+        * Action command pattern used to execute command.
+        */
+       const ACTION_COMMAND_PATTERN = "%s%s 2>&1";
+
+       /**
+        * Get command pattern.
+        *
+        * @return string command pattern
+        */
+       private function getCmdPattern() {
+               // Default command pattern
+               return self::ACTION_COMMAND_PATTERN;
+       }
+
+       /**
+        * Get sudo command.
+        *
+        * @param boolean $use_sudo sudo option state
+        * @return string sudo command
+        */
+       private function getSudo($use_sudo) {
+               $sudo = '';
+               if ($use_sudo === true) {
+                       $sudo = self::SUDO . ' ';
+               }
+               return $sudo;
+       }
+
+       /**
+        * Execute single action command.
+        *
+        * @param string $action_type action type (dir_start, dir_stop ...etc.)
+        * @return array result with output and exitcode
+        */
+       public function execAction($action_type) {
+               $result = null;
+               $output = array();
+               $exitcode = -1;
+               $api_config = $this->getModule('api_config');
+               if ($api_config->isActionConfigured($action_type) === true) {
+                       if ($api_config->isActionsEnabled() === true) {
+                               $action = $api_config->getActionConfig($action_type);
+                               $result = $this->execCommand($action['cmd'], $action['use_sudo']);
+                               if ($result->error !== 0) {
+                                       $emsg = PHP_EOL . ' Output:' . implode(PHP_EOL, $result->output);
+                                       $output = ActionsError::MSG_ERROR_ACTIONS_WRONG_EXITCODE . $emsg;
+                                       $exitcode = ActionsError::ERROR_ACTIONS_WRONG_EXITCODE;
+                                       $result = $this->prepareResult($output, $exitcode);
+                               }
+                       } else {
+                               $output = ActionsError::MSG_ERROR_ACTIONS_DISABLED;
+                               $exitcode = ActionsError::ERROR_ACTIONS_DISABLED;
+                               $result = $this->prepareResult($output, $exitcode);
+                       }
+               } else {
+                       $output = ActionsError::MSG_ERROR_ACTIONS_NOT_CONFIGURED;
+                       $exitcode = ActionsError::ERROR_ACTIONS_NOT_CONFIGURED;
+                       $result = $this->prepareResult($output, $exitcode);
+               }
+               return $result;
+       }
+
+       /**
+        * Execute action command.
+        *
+        * @param string $bin command
+        * @param boolean $use_sudo use sudo
+        */
+       public function execCommand($bin, $use_sudo) {
+               $sudo = $this->getSudo($use_sudo);
+               $cmd_pattern = $this->getCmdPattern();
+               $cmd = sprintf($cmd_pattern, $sudo, $bin);
+               exec($cmd, $output, $exitcode);
+               $this->getModule('logging')->log($cmd, $output, Logging::CATEGORY_EXECUTE, __FILE__, __LINE__);
+               $result = $this->prepareResult($output, $exitcode);
+               return $result;
+       }
+
+       /**
+        * Prepare action command result.
+        *
+        * @param array $output output from command execution
+        * @param integer $exitcode command exit code
+        * @return array result with output and exitcode
+        */
+       public function prepareResult($output, $exitcode) {
+               $result = (object)array(
+                       'output' => $output,
+                       'error' => $exitcode
+               );
+               return $result;
+       }
+
+}
+?>
index 8c7b21c69e0f1c3ebdc4ca946af436e95a571e10..22c9aa5d38602e22f2a36c8a02a2323e9a48302f 100644 (file)
@@ -19,7 +19,8 @@ var OAuth2Scopes = [
        'filesets',
        'schedules',
        'config',
-       'status'
+       'status',
+       'actions'
 ];
 var set_scopes = function(field_id) {
        document.getElementById(field_id).value = OAuth2Scopes.join(' ');
index f5f96700490812d024eeead2886903e9968dbf4f..5f2c944a5c306ec41e0fb21eb4423ee69aaeb8b4 100644 (file)
Binary files a/gui/baculum/protected/API/Lang/en/messages.mo and b/gui/baculum/protected/API/Lang/en/messages.mo differ
index 60a13bc463fc9a306d7ba50a31a2aaf12b330dd5..59faa6de5fb8cc307432232f76d64d650f5b9001 100644 (file)
@@ -194,18 +194,12 @@ msgstr "Config API"
 msgid "Error"
 msgstr "Error"
 
-msgid "Authorization"
-msgstr "Authorization"
-
 msgid "Finish"
 msgstr "Finish"
 
 msgid "Do you want to setup and share the Bacula configuration interface to configure Bacula components via this API instance?"
 msgstr "Do you want to setup and share the Bacula configuration interface to configure Bacula components via this API instance?"
 
-msgid "Step 5 - authorization to API"
-msgstr "Step 5 - authorization to API"
-
 msgid "Administration login:"
 msgstr "Administration login:"
 
@@ -242,17 +236,11 @@ msgstr "Database file path (SQLite only):"
 msgid "Use sudo for bconsole requests:"
 msgstr "Use sudo for bconsole requests:"
 
-msgid "Authorization to Baculum REST API"
-msgstr "Authorization to Baculum REST API"
-
 msgid "Save"
 msgstr "Save"
 
-msgid "Authorization type:"
-msgstr "Authorization type:"
-
-msgid "Step 6 - Finish"
-msgstr "Step 6 - Finish"
+msgid "Step 7 - Finish"
+msgstr "Step 7 - Finish"
 
 msgid "Use sudo for Bacula JSON tools:"
 msgstr "Use sudo for Bacula JSON tools:"
@@ -439,3 +427,87 @@ msgstr "In this directory Baculum API saves temporarily Bacula configuration fil
 
 msgid "Version:"
 msgstr "Version:"
+
+msgid "Authentication"
+msgstr "Authentication"
+
+msgid "Authentication to Baculum REST API"
+msgstr "Authentication to Baculum REST API"
+
+msgid "Authentication type:"
+msgstr "Authentication type:"
+
+msgid "Step 6 - authentication to API"
+msgstr "Step 6 - authentication to API"
+
+msgid "Actions"
+msgstr "Actions"
+
+msgid "Start"
+msgstr "Start"
+
+msgid "Stop"
+msgstr "Stop"
+
+msgid "Restart"
+msgstr "Restart"
+
+msgid "Director start command:"
+msgstr "Director start command:"
+
+msgid "Director stop command:"
+msgstr "Director stop command:"
+
+msgid "Director restart command:"
+msgstr "Director restart command:"
+
+msgid "Storage Daemon start command:"
+msgstr "Storage Daemon start command:"
+
+msgid "Storage Daemon stop command:"
+msgstr "Storage Daemon stop command:"
+
+msgid "Storage Daemon restart command:"
+msgstr "Storage Daemon restart command:"
+
+msgid "File Daemon/Client start command:"
+msgstr "File Daemon/Client start command:"
+
+msgid "File Daemon/Client stop command:"
+msgstr "File Daemon/Client stop command:"
+
+msgid "File Daemon/Client restart command:"
+msgstr "File Daemon/Client restart command:"
+
+msgid "Do you want to setup start, stop and restart actions for Bacula components? If you define them, there will be possible to call these actions via API interface and by Baculum Web as well."
+msgstr "Do you want to setup start, stop and restart actions for Bacula components? If you define them, there will be possible to call these actions via API interface and by Baculum Web as well."
+
+msgid "Sudo configuration"
+msgstr "Sudo configuration"
+
+msgid "Example sudo configuration for Apache web server user (RHEL, CentOS and others):"
+msgstr "Example sudo configuration for Apache web server user (RHEL, CentOS and others):"
+
+msgid "Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others):"
+msgstr "Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others):"
+
+msgid "Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others):"
+msgstr "Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others):"
+
+msgid "Get sudo configuration"
+msgstr "Get sudo configuration"
+
+msgid "Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api"
+msgstr "Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api"
+
+msgid "Step 5 - enable actions for components"
+msgstr "Step 5 - enable actions for components"
+
+msgid "Directory path for new config files:"
+msgstr "Directory path for new config files:"
+
+msgid "Use sudo for actions:"
+msgstr "Use sudo for actions:"
+
+msgid "Actions setting:"
+msgstr "Actions setting:"
index b8375ed654cd14d1f83e0597e93fa303d51542d0..631790be8bb1ae7d3e55060512b79226cdfb29b9 100644 (file)
Binary files a/gui/baculum/protected/API/Lang/pl/messages.mo and b/gui/baculum/protected/API/Lang/pl/messages.mo differ
index 2496aa75273f11761c69447618f52fd616362522..dd1facc3d1945dbbe013b5b9d9bea57fe63d1c5f 100644 (file)
@@ -194,18 +194,12 @@ msgstr "Config API"
 msgid "Error"
 msgstr "Błąd"
 
-msgid "Authorization"
-msgstr "Autoryzacja"
-
 msgid "Finish"
 msgstr "Koniec"
 
 msgid "Do you want to setup and share the Bacula configuration interface to configure Bacula components via this API instance?"
 msgstr "Czy chcesz skonfigurować i udostÄ™pnić interfejs konfiguracyjny Bacula do konfiguracji komponentów Bacula poprzez tÄ… instancjÄ™ API?"
 
-msgid "Step 5 - authorization to API"
-msgstr "Krok 5 - autoryzacja do API"
-
 msgid "Administration login:"
 msgstr "Login administratora:"
 
@@ -242,17 +236,11 @@ msgstr "Lokalizacja pliku DB (tylko SQLite):"
 msgid "Use sudo for bconsole requests:"
 msgstr "Użyj sudo dla zapytaÅ„ bconsole:"
 
-msgid "Authorization to Baculum REST API"
-msgstr "Autoryzacja do Baculum REST API"
-
 msgid "Save"
 msgstr "Zapisz"
 
-msgid "Authorization type:"
-msgstr "Typ autoryzacji:"
-
-msgid "Step 6 - Finish"
-msgstr "Krok 6 - Koniec"
+msgid "Step 7 - Finish"
+msgstr "Krok 7 - Koniec"
 
 msgid "Use sudo for Bacula JSON tools:"
 msgstr "Użyj sudo dla narzÄ™dzi JSON Bacula."
@@ -439,3 +427,87 @@ msgstr "W tym katalogu Baculum API tymczasowo zapisuje pliki konfiguracyjne Bacu
 
 msgid "Version:"
 msgstr "Wersja:"
+
+msgid "Authentication"
+msgstr "Uwierzytelnianie"
+
+msgid "Authentication to Baculum REST API"
+msgstr "Uwierzytelnianie do Baculum REST API"
+
+msgid "Authentication type:"
+msgstr "Typ uwierzytelniania:"
+
+msgid "Step 6 - authentication to API"
+msgstr "Krok 6 - uwierzytelnianie do API"
+
+msgid "Actions"
+msgstr "Actions"
+
+msgid "Start"
+msgstr "Start"
+
+msgid "Stop"
+msgstr "Stop"
+
+msgid "Restart"
+msgstr "Restart"
+
+msgid "Director start command:"
+msgstr "Komenda uruchomenia Director:"
+
+msgid "Director stop command:"
+msgstr "Komenda zatrzymania Director:"
+
+msgid "Director restart command:"
+msgstr "Komenda restartu Director:"
+
+msgid "Storage Daemon start command:"
+msgstr "Komenda uruchomienia Storage Daemon:"
+
+msgid "Storage Daemon stop command:"
+msgstr "Komenda zatrzymania Storage Daemon:"
+
+msgid "Storage Daemon restart command:"
+msgstr "Komenda restartu Storage Daemon:"
+
+msgid "File Daemon/Client start command:"
+msgstr "Komenda uruchomienia klienta:"
+
+msgid "File Daemon/Client stop command:"
+msgstr "Komenda zatrzymania klienta:"
+
+msgid "File Daemon/Client restart command:"
+msgstr "Komenda restartu klienta:"
+
+msgid "Do you want to setup start, stop and restart actions for Bacula components? If you define them, there will be possible to call these actions via API interface and by Baculum Web as well."
+msgstr "Czy chcesz skonfigurować akcje uruchomienia, zatrzymania i restartu komponentów Bacula? JeÅ›li zdefiniujesz je, wtedy bÄ™dzie możliwe wykonywanie tych akcji poprzez interfejs API i również przez Baculum Web."
+
+msgid "Sudo configuration"
+msgstr "Konfiguracja sudo"
+
+msgid "Example sudo configuration for Apache web server user (RHEL, CentOS and others):"
+msgstr "PrzykÅ‚adowa konfiguracja sudo dla użytkownika serwera web Apache (RHEL, CentOS i inne):"
+
+msgid "Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others):"
+msgstr "PrzykÅ‚adowa konfiguracja sudo dla użytkownika serwera web Lighttpd (RHEL, CentOS i inne):"
+
+msgid "Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others):"
+msgstr "PrzykÅ‚adowa konfiguracja sudo dla użytkownika serwerów web Apache i Lighttpd (Debian, Ubuntu i inne):"
+
+msgid "Get sudo configuration"
+msgstr "Weź konfiguracjÄ™ sudo"
+
+msgid "Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api"
+msgstr "ProszÄ™ skopiować odpowiedniÄ… konfiguracjÄ™ sudo i wpisać jÄ… do nowego pliku sudoers.d na przykÅ‚ad /etc/sudoers.d/baculum-api"
+
+msgid "Step 5 - enable actions for components"
+msgstr "Krok 5 - włącz akcje dla komponentów"
+
+msgid "Directory path for new config files:"
+msgstr "Lokalizacja katalogu na nowe pliki konfiguracyjne:"
+
+msgid "Use sudo for actions:"
+msgstr "Użyj sudo dla akcji:"
+
+msgid "Actions setting:"
+msgstr "Ustawienie akcji:"
index 395b4cbf904a0827156bb5453d1c9da449c4a823..e9d2ebef5ffd5ba3df4b8db6dd867c4c268f5dfe 100644 (file)
Binary files a/gui/baculum/protected/API/Lang/pt/messages.mo and b/gui/baculum/protected/API/Lang/pt/messages.mo differ
index 5ec37453da80344375dfc9f85a093b2f9fbb87a2..aa0c801941c01938616c791a87d0775e0b6a9b86 100644 (file)
@@ -194,18 +194,12 @@ msgstr "Configurar"
 msgid "Error"
 msgstr "Erro"
 
-msgid "Authorization"
-msgstr "Autorização"
-
 msgid "Finish"
 msgstr "Finalizar"
 
 msgid "Do you want to setup and share the Bacula configuration interface to configure Bacula components via this API instance?"
 msgstr "Deseja configurar e compartilhar a interface de configuração do Bacula para configurar os recursos através desta instância da API?"
 
-msgid "Step 5 - authorization to API"
-msgstr "Passo 5 - Autorização da API"
-
 msgid "Administration login:"
 msgstr "Usuário administrador:"
 
@@ -242,17 +236,11 @@ msgstr "Caminho para o banco de dados (SQLite somente):"
 msgid "Use sudo for bconsole requests:"
 msgstr "Utilizar sudo para executar bconsole:"
 
-msgid "Authorization to Baculum REST API"
-msgstr "Autorização para a API REST do Baculum"
-
 msgid "Save"
 msgstr "Salvar"
 
-msgid "Authorization type:"
-msgstr "Tipo de autorização:"
-
-msgid "Step 6 - Finish"
-msgstr "Passo 6 - Finalizar"
+msgid "Step 7 - Finish"
+msgstr "Passo 7 - Finalizar"
 
 msgid "Use sudo for Bacula JSON tools:"
 msgstr "Utilizar o sudo para os utilitários JSON:"
@@ -439,3 +427,87 @@ msgstr "In this directory Baculum API saves temporarily Bacula configuration fil
 
 msgid "Version:"
 msgstr "Version:"
+
+msgid "Authentication"
+msgstr "Authentication"
+
+msgid "Authentication to Baculum REST API"
+msgstr "Authentication to Baculum REST API"
+
+msgid "Authentication type:"
+msgstr "Authentication type:"
+
+msgid "Step 6 - authentication to API"
+msgstr "Step 6 - authentication to API"
+
+msgid "Actions"
+msgstr "Actions"
+
+msgid "Start"
+msgstr "Start"
+
+msgid "Stop"
+msgstr "Stop"
+
+msgid "Restart"
+msgstr "Restart"
+
+msgid "Director start command:"
+msgstr "Director start command:"
+
+msgid "Director stop command:"
+msgstr "Director stop command:"
+
+msgid "Director restart command:"
+msgstr "Director restart command:"
+
+msgid "Storage Daemon start command:"
+msgstr "Storage Daemon start command:"
+
+msgid "Storage Daemon stop command:"
+msgstr "Storage Daemon stop command:"
+
+msgid "Storage Daemon restart command:"
+msgstr "Storage Daemon restart command:"
+
+msgid "File Daemon/Client start command:"
+msgstr "File Daemon/Client start command:"
+
+msgid "File Daemon/Client stop command:"
+msgstr "File Daemon/Client stop command:"
+
+msgid "File Daemon/Client restart command:"
+msgstr "File Daemon/Client restart command:"
+
+msgid "Do you want to setup start, stop and restart actions for Bacula components? If you define them, there will be possible to call these actions via API interface and by Baculum Web as well."
+msgstr "Do you want to setup start, stop and restart actions for Bacula components? If you define them, there will be possible to call these actions via API interface and by Baculum Web as well."
+
+msgid "Sudo configuration"
+msgstr "Sudo configuration"
+
+msgid "Example sudo configuration for Apache web server user (RHEL, CentOS and others):"
+msgstr "Example sudo configuration for Apache web server user (RHEL, CentOS and others):"
+
+msgid "Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others):"
+msgstr "Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others):"
+
+msgid "Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others):"
+msgstr "Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others):"
+
+msgid "Get sudo configuration"
+msgstr "Get sudo configuration"
+
+msgid "Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api"
+msgstr "Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api"
+
+msgid "Step 5 - enable actions for components"
+msgstr "Step 5 - enable actions for components"
+
+msgid "Directory path for new config files:"
+msgstr "Directory path for new config files:"
+
+msgid "Use sudo for actions:"
+msgstr "Use sudo for actions:"
+
+msgid "Actions setting:"
+msgstr "Actions setting:"
diff --git a/gui/baculum/protected/API/Pages/API/Actions.php b/gui/baculum/protected/API/Pages/API/Actions.php
new file mode 100644 (file)
index 0000000..34db5be
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2019 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.API.Class.APIConfig');
+class Actions extends BaculumAPIServer {
+
+       public function get() {
+               $component = $this->Request->contains('component') ? $this->Request['component'] : '';
+               $action = $this->Request->contains('action') ? $this->Request['action'] : '';
+               $action_type = null;
+
+               switch ($component) {
+                       case 'director':
+                               if ($action === 'start') {
+                                       $action_type = APIConfig::ACTION_DIR_START;
+                               } elseif ($action === 'stop') {
+                                       $action_type = APIConfig::ACTION_DIR_STOP;
+                               } elseif ($action === 'restart') {
+                                       $action_type = APIConfig::ACTION_DIR_RESTART;
+                               }
+                               break;
+                       case 'storage':
+                               if ($action === 'start') {
+                                       $action_type = APIConfig::ACTION_SD_START;
+                               } elseif ($action === 'stop') {
+                                       $action_type = APIConfig::ACTION_SD_STOP;
+                               } elseif ($action === 'restart') {
+                                       $action_type = APIConfig::ACTION_SD_RESTART;
+                               }
+                               break;
+                       case 'client':
+                               if ($action === 'start') {
+                                       $action_type = APIConfig::ACTION_FD_START;
+                               } elseif ($action === 'stop') {
+                                       $action_type = APIConfig::ACTION_FD_STOP;
+                               } elseif ($action === 'restart') {
+                                       $action_type = APIConfig::ACTION_FD_RESTART;
+                               }
+                               break;
+               }
+
+               if (is_string($action_type)) {
+                       $result = $this->getModule('comp_actions')->execAction($action_type);
+                       if ($result->error === 0) {
+                               $this->output = ActionsError::MSG_ERROR_NO_ERRORS;
+                               $this->error = ActionsError::ERROR_NO_ERRORS;
+                       } else {
+                               $this->output = $result->output;
+                               $this->error = $result->error;
+                       }
+               } else {
+                       $this->output = ActionsError::MSG_ERROR_ACTIONS_ACTION_DOES_NOT_EXIST;
+                       $this->error = ActionsError::ERROR_ACTIONS_ACTION_DOES_NOT_EXIST;
+               }
+       }
+}
+?>
index 8a86a2d9dd08d08700f91cd8f07d8eaeea00a933..13af87270a2f9353f5ac14f97cbee7c983411c48 100644 (file)
                                        <div><com:TTranslate Text="Config API" /></div>
                                </div>
                                <div class="step step-<%=($this->Parent->ActiveStepIndex === 4) ? 'active' : (($this->Parent->ActiveStepIndex === 5) ? 'prev-active' : 'normal')%>">
-                                       <div><com:TTranslate Text="Authorization" /></div>
+                                       <div><com:TTranslate Text="Actions" /></div>
                                </div>
-                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 5) ? 'last-active' : (($this->Parent->ActiveStepIndex === 6) ? 'last-prev-active' : 'last')%>">
+                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 5) ? 'active' : (($this->Parent->ActiveStepIndex === 6) ? 'prev-active' : 'normal')%>">
+                                       <div><com:TTranslate Text="Authentication" /></div>
+                               </div>
+                               <div class="step step-<%=($this->Parent->ActiveStepIndex === 6) ? 'last-active' : (($this->Parent->ActiveStepIndex === 7) ? 'last-prev-active' : 'last')%>">
                                        <div><com:TTranslate Text="Finish" /></div>
                                </div>
                                <div id="title"><%=$this->Parent->ActiveStep->Title%><div>
                                </div>
                        </div>
                </com:TWizardStep>
-               <com:TWizardStep ID="Step5" Title="<%[ Step 5 - authorization to API ]%>" StepType="Auto">
+               <com:TWizardStep ID="Step5" Title="<%[ Step 5 - enable actions for components ]%>" StepType="Auto">
+                       <p><%[ Do you want to setup start, stop and restart actions for Bacula components? If you define them, there will be possible to call these actions via API interface and by Baculum Web as well. ]%></p>
+                       <div class="line left">
+                               <com:TRadioButton ID="ActionsNo" GroupName="SelectActions" Attributes.onclick="$('#configure_actions').hide();" /> <com:TLabel ForControl="ActionsNo" Text="<%[ No ]%>" />
+                       </div>
+                       <div class="line left">
+                               <com:TRadioButton ID="ActionsYes" GroupName="SelectActions" Attributes.onclick="$('#configure_actions').show();" /> <com:TLabel ForControl="ActionsYes" Text="<%[ Yes ]%>" />
+                       </div>
+                       <div id="configure_actions" style="display: <%=($this->ActionsYes->Checked === true) ? 'block' : 'none';%>">
+                               <div id="actions_fields">
+                                       <fieldset class="actions_field">
+                                               <legend><%[ General configuration ]%></legend>
+                                               <div class="actions_lines">
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="ActionsUseSudo" Text="<%[ Use sudo: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TCheckBox ID="ActionsUseSudo" /> <a href="javascript:void(0)" onclick="get_sudo_config();"><%[ Get sudo configuration ]%></a>
+                                                               </div>
+                                                       </div>
+                                               </div>
+                                       </fieldset>
+                                       <fieldset class="actions_field">
+                                               <legend>Director</legend>
+                                               <div class="actions_lines">
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="DirStartAction" Text="<%[ Director start command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="DirStartAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestDirStartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Start ]%>" OnCommand="testExecActionCommand" CommandParameter="dir_start">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestDirStartActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_dir_start .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_dir_start .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_dir_start">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="DirStopAction" Text="<%[ Director stop command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="DirStopAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestDirStopActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Stop ]%>" OnCommand="testExecActionCommand" CommandParameter="dir_stop">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestDirStopActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_dir_stop .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_dir_stop .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_dir_stop">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="DirRestartAction" Text="<%[ Director restart command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="DirRestartAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestDirRestartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Restart ]%>" OnCommand="testExecActionCommand" CommandParameter="dir_restart">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestDirRestartActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_dir_restart .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_dir_restart .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_dir_restart">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                               </div>
+                                       </fieldset>
+                                       <fieldset class="actions_field">
+                                               <legend>Storage Daemon</legend>
+                                               <div class="actions_lines">
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="SdStartAction" Text="<%[ Storage Daemon start command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="SdStartAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestSdStartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Start ]%>" OnCommand="testExecActionCommand" CommandParameter="sd_start">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestSdStartActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_sd_start .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_sd_start .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_sd_start">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="SdStopAction" Text="<%[ Storage Daemon stop command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="SdStopAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestSdStopActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Stop ]%>" OnCommand="testExecActionCommand" CommandParameter="sd_stop">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestSdStopActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_sd_stop .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_sd_stop .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_sd_stop">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="SdRestartAction" Text="<%[ Storage Daemon restart command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="SdRestartAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestSdRestartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Restart ]%>" OnCommand="testExecActionCommand" CommandParameter="sd_restart">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestSdRestartActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_sd_restart .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_sd_restart .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_sd_restart">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                               </div>
+                                       </fieldset>
+                                       <fieldset class="actions_field">
+                                               <legend>File Daemon/Client</legend>
+                                               <div class="actions_lines">
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="FdStartAction" Text="<%[ File Daemon/Client start command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="FdStartAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestFdStartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Start ]%>" OnCommand="testExecActionCommand" CommandParameter="fd_start">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestFdStartActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_fd_start .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_fd_start .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_fd_start">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="FdStopAction" Text="<%[ File Daemon/Client stop command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="FdStopAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestFdStopActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Stop ]%>" OnCommand="testExecActionCommand" CommandParameter="fd_stop">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestFdStopActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_fd_stop .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_fd_stop .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_fd_stop">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                                       <div class="line left">
+                                                               <div class="text"><com:TLabel ForControl="FdRestartAction" Text="<%[ File Daemon/Client restart command: ]%>" /></div>
+                                                               <div class="field">
+                                                                       <com:TTextBox ID="FdRestartAction" CssClass="textbox" />
+                                                               </div>
+                                                               <div class="button">
+                                                                       <com:TActiveButton ID="TestFdRestartActionCommand" ValidationGroup="ActionsGroup" Text="<%[ Restart ]%>" OnCommand="testExecActionCommand" CommandParameter="fd_restart">
+                                                                               <prop:ClientSide.OnLoading>
+                                                                                       actions_hide_test_results('<%=$this->TestFdRestartActionCommand->CommandParameter%>');
+                                                                                       $('#actions_test_result_fd_restart .action_test_loader').show();
+                                                                               </prop:ClientSide.OnLoading>
+                                                                               <prop:ClientSide.OnComplete>
+                                                                                       $('#actions_test_result_fd_restart .action_test_loader').hide();
+                                                                               </prop:ClientSide.OnComplete>
+                                                                       </com:TActiveButton>
+                                                               </div>
+                                                               <span id="actions_test_result_fd_restart">
+                                                                       <span class="action_test_loader" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader.gif" alt="<%[ Loading... ]%>" /></span>
+                                                                       <span class="action_success" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ OK ]%>" /> <%[ OK ]%></span>
+                                                                       <span class="action_error" style="display: none"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Error ]%>" /> <%[ Error ]%></span>
+                                                                       <span class="action_result validator"></span>
+                                                               </span>
+                                                       </div>
+                                               </div>
+                                       </fieldset>
+                               </div>
+                       </div>
+               </com:TWizardStep>
+               <com:TWizardStep ID="Step6" Title="<%[ Step 6 - authentication to API ]%>" StepType="Auto">
                        <div class="line left" style="width:500px; margin: 0 auto">
                                <com:TActiveRadioButton
                                        ID="AuthOAuth2"
                                }
                        </script>
                </com:TWizardStep>
-               <com:TWizardStep ID="Step6" Title="<%[ Step 6 - Finish ]%>" StepType="Finish">
+               <com:TWizardStep ID="Step7" Title="<%[ Step 7 - Finish ]%>" StepType="Finish">
                        <fieldset>
                                <legend><%[ Catalog API ]%></legend>
                                <div style="display: <%=($this->DatabaseYes->Checked ? 'block' : 'none')%>">
                                </div>
                        </fieldset>
                        <fieldset>
-                               <legend><%[ Authorization to Baculum REST API ]%></legend>
+                               <legend><%[ Actions ]%></legend>
+                               <div style="display: <%=($this->ActionsYes->Checked ? 'block' : 'none')%>">
+                                       <div class="line">
+                                               <div class="text"><%[ Use sudo for actions: ]%></div>
+                                               <div class="field bold"><%=($this->ActionsUseSudo->Checked === true) ? 'yes' : 'no'%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ Director start command: ]%></div>
+                                               <div class="field bold"><%=$this->DirStartAction->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ Director stop command: ]%></div>
+                                               <div class="field bold"><%=$this->DirStopAction->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ Director restart command: ]%></div>
+                                               <div class="field bold"><%=$this->DirRestartAction->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ Storage Daemon start command: ]%></div>
+                                               <div class="field bold"><%=$this->SdStartAction->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ Storage Daemon stop command: ]%></div>
+                                               <div class="field bold"><%=$this->SdStopAction->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ Storage Daemon restart command: ]%></div>
+                                               <div class="field bold"><%=$this->SdRestartAction->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ File Daemon/Client start command: ]%></div>
+                                               <div class="field bold"><%=$this->FdStartAction->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ File Daemon/Client stop command: ]%></div>
+                                               <div class="field bold"><%=$this->FdStopAction->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ File Daemon/Client restart command: ]%></div>
+                                               <div class="field bold"><%=$this->FdRestartAction->Text%></div>
+                                       </div>
+                               </div>
+                               <div style="display: <%=($this->ActionsNo->Checked ? 'block' : 'none')%>">
+                                       <div class="line">
+                                               <div class="text"><%[ Actions setting: ]%></div>
+                                               <div class="field bold"><%[ Disabled ]%></div>
+                                       </div>
+                               </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend><%[ Authentication to Baculum REST API ]%></legend>
                                <div class="line">
-                                       <div class="text"><%[ Authorization type: ]%></div>
+                                       <div class="text"><%[ Authentication type: ]%></div>
                                        <div class="field bold">
                                                <%=$this->AuthBasic->Checked ? 'HTTP Basic' : ''%>
                                                <%=$this->AuthOAuth2->Checked ? 'OAuth2' : ''%>
                        </fieldset>
                </com:TWizardStep>
        </com:TWizard>
+<com:TJuiDialog
+       ID="SudoConfigPopup"
+       Options.title="<%[ Sudo configuration ]%>"
+       Options.autoOpen="false",
+       Options.minWidth="600"
+       Options.minHeight="200"
+>
+       <p><%[ Please copy appropriate sudo configuration and put it to a new sudoers.d file for example /etc/sudoers.d/baculum-api ]%></p>
+       <p><%[ Example sudo configuration for Apache web server user (RHEL, CentOS and others): ]%></p>
+       <pre id="sudo_config_apache"></pre>
+       <p><%[ Example sudo configuration for Lighttpd web server user (RHEL, CentOS and others): ]%></p>
+       <pre id="sudo_config_lighttpd"></pre>
+       <p><%[ Example sudo configuration for Apache and Lighttpd web servers user (Debian, Ubuntu and others): ]%></p>
+       <pre id="sudo_config_www_data"></pre>
+</com:TJuiDialog>
 <script type="text/javascript">
 var bjsontools_fields = {
        General: {
@@ -931,5 +1246,57 @@ var bjsontools_hide_test_results = function(field_type) {
                }
        }
 };
+
+var set_action_command_output = function(action, result) {
+       var result_el = document.querySelector('#actions_test_result_' + action + ' .action_result');
+       if (result.error === 0) {
+               var success = document.querySelector('#actions_test_result_' + action + ' .action_success');
+               success.style.display = '';
+               result_el.textContent = '';
+       } else {
+               var error = document.querySelector('#actions_test_result_' + action + ' .action_error');
+               error.style.display = '';
+               result_el.innerHTML = '<br />' + result.output.join('<br />');
+       }
+};
+
+var actions_hide_test_results = function(action) {
+       var loader = document.querySelector('#actions_test_result_' + action + ' .action_test_loader');
+       var success = document.querySelector('#actions_test_result_' + action + ' .action_success');
+       var error = document.querySelector('#actions_test_result_' + action + ' .action_error');
+       var result = document.querySelector('#actions_test_result_' + action + ' .action_result');
+       loader.style.display = 'none';
+       success.style.display = 'none'
+       error.style.display = 'none';
+       result.textContent = '';
+};
+
+function get_sudo_config() {
+       var fields = [
+               '<%=$this->DirStartAction->ClientID%>',
+               '<%=$this->DirStopAction->ClientID%>',
+               '<%=$this->DirRestartAction->ClientID%>',
+               '<%=$this->SdStartAction->ClientID%>',
+               '<%=$this->SdStopAction->ClientID%>',
+               '<%=$this->SdRestartAction->ClientID%>',
+               '<%=$this->FdStartAction->ClientID%>',
+               '<%=$this->FdStopAction->ClientID%>',
+               '<%=$this->FdRestartAction->ClientID%>'
+       ];
+       var val, pre;
+       var cfg = '';
+       var users = ['apache', 'lighttpd', 'www-data'];
+       for (var i = 0; i < users.length; i++) {
+               var pre = document.getElementById('sudo_config_' + users[i].replace(/-/g, '_'));
+               pre.textContent = 'Defaults:' + users[i] + ' !requiretty' + "\n";
+               for (var j = 0; j < fields.length; j++) {
+                       val = document.getElementById(fields[j]).value.trim();
+                       if (val) {
+                               pre.textContent += users[i] + ' ALL = (root) NOPASSWD: ' + val + "\n";
+                       }
+               }
+       }
+       $('#<%=$this->SudoConfigPopup->ClientID%>').dialog('open');
+}
 </script>
 </com:TContent>
index f1cd2947b79d7860c62188a5ae64b53be0987550..7a507e21cd1427bc775a5a2c31a243b468e2178d 100644 (file)
@@ -53,6 +53,16 @@ class APIInstallWizard extends BaculumAPIPage {
        const DEFAULT_FD_CONF = '/etc/bacula/bacula-fd.conf';
        const DEFAULT_BBCONJSON_BIN = '/usr/sbin/bbconsjson';
 
+       const DEFAULT_ACTION_DIR_START = '/usr/bin/systemctl start bacula-dir';
+       const DEFAULT_ACTION_DIR_STOP = '/usr/bin/systemctl stop bacula-dir';
+       const DEFAULT_ACTION_DIR_RESTART = '/usr/bin/systemctl restart bacula-dir';
+       const DEFAULT_ACTION_SD_START = '/usr/bin/systemctl start bacula-sd';
+       const DEFAULT_ACTION_SD_STOP = '/usr/bin/systemctl stop bacula-sd';
+       const DEFAULT_ACTION_SD_RESTART = '/usr/bin/systemctl restart bacula-sd';
+       const DEFAULT_ACTION_FD_START = '/usr/bin/systemctl start bacula-fd';
+       const DEFAULT_ACTION_FD_STOP = '/usr/bin/systemctl stop bacula-fd';
+       const DEFAULT_ACTION_FD_RESTART = '/usr/bin/systemctl restart bacula-fd';
+
        public function onPreInit($param) {
                parent::onPreInit($param);
                if (isset($_SESSION['language'])) {
@@ -89,9 +99,21 @@ class APIInstallWizard extends BaculumAPIPage {
                                $this->FdCfgPath->Text = self::DEFAULT_FD_CONF;
                                $this->BBconsJSONPath->Text = self::DEFAULT_BBCONJSON_BIN;
                                $this->BconsCfgPath->Text = self::DEFAULT_BCONSOLE_CONF;
+
+                               $this->DirStartAction->Text = self::DEFAULT_ACTION_DIR_START;
+                               $this->DirStopAction->Text = self::DEFAULT_ACTION_DIR_STOP;
+                               $this->DirRestartAction->Text = self::DEFAULT_ACTION_DIR_RESTART;
+                               $this->SdStartAction->Text = self::DEFAULT_ACTION_SD_START;
+                               $this->SdStopAction->Text = self::DEFAULT_ACTION_SD_STOP;
+                               $this->SdRestartAction->Text = self::DEFAULT_ACTION_SD_RESTART;
+                               $this->FdStartAction->Text = self::DEFAULT_ACTION_FD_START;
+                               $this->FdStopAction->Text = self::DEFAULT_ACTION_FD_STOP;
+                               $this->FdRestartAction->Text = self::DEFAULT_ACTION_FD_RESTART;
+
                                $this->DatabaseNo->Checked = true;
                                $this->ConsoleNo->Checked = true;
                                $this->ConfigNo->Checked = true;
+                               $this->ActionsNo->Checked = true;
                        } else {
                                // Database param settings
                                if ($this->config['db']['enabled'] == 1) {
@@ -122,8 +144,10 @@ class APIInstallWizard extends BaculumAPIPage {
                                $this->BconsoleConfigPath->Text = $this->config['bconsole']['cfg_path'];
                                $this->UseSudo->Checked = $this->getPage()->config['bconsole']['use_sudo'] == 1;
 
+                               $api_config = $this->getModule('api_config');
+
                                // JSONTools param settings
-                               if ($this->config['jsontools']['enabled'] == 1) {
+                               if ($api_config->isJSONToolsEnabled() === true) {
                                        $this->ConfigYes->Checked = true;
                                        $this->ConfigNo->Checked = false;
                                } else {
@@ -141,6 +165,41 @@ class APIInstallWizard extends BaculumAPIPage {
                                $this->BBconsJSONPath->Text = $this->config['jsontools']['bbconsjson_path'];
                                $this->BconsCfgPath->Text = $this->config['jsontools']['bcons_cfg_path'];
 
+                               if ($api_config->isActionsConfigured()) {
+                                       // Action params
+                                       if ($api_config->isActionsEnabled() === true) {
+                                               $this->ActionsYes->Checked = true;
+                                               $this->ActionsNo->Checked = false;
+                                       } else {
+                                               $this->ActionsYes->Checked = false;
+                                               $this->ActionsNo->Checked = true;
+                                       }
+
+                                       $this->ActionsUseSudo->Checked = ($this->config['actions']['use_sudo'] == 1);
+                                       $this->DirStartAction->Text = $this->config['actions']['dir_start'];
+                                       $this->DirStopAction->Text = $this->config['actions']['dir_stop'];
+                                       $this->DirRestartAction->Text = $this->config['actions']['dir_restart'];
+                                       $this->SdStartAction->Text = $this->config['actions']['sd_start'];
+                                       $this->SdStopAction->Text = $this->config['actions']['sd_stop'];
+                                       $this->SdRestartAction->Text = $this->config['actions']['sd_restart'];
+                                       $this->FdStartAction->Text = $this->config['actions']['fd_start'];
+                                       $this->FdStopAction->Text = $this->config['actions']['fd_stop'];
+                                       $this->FdRestartAction->Text = $this->config['actions']['fd_restart'];
+                               } else {
+                                       $this->ActionsYes->Checked = false;
+                                       $this->ActionsNo->Checked = true;
+                                       $this->ActionsUseSudo->Checked = false;
+                                       $this->DirStartAction->Text = self::DEFAULT_ACTION_DIR_START;
+                                       $this->DirStopAction->Text = self::DEFAULT_ACTION_DIR_STOP;
+                                       $this->DirRestartAction->Text = self::DEFAULT_ACTION_DIR_RESTART;
+                                       $this->SdStartAction->Text = self::DEFAULT_ACTION_SD_START;
+                                       $this->SdStopAction->Text = self::DEFAULT_ACTION_SD_STOP;
+                                       $this->SdRestartAction->Text = self::DEFAULT_ACTION_SD_RESTART;
+                                       $this->FdStartAction->Text = self::DEFAULT_ACTION_FD_START;
+                                       $this->FdStopAction->Text = self::DEFAULT_ACTION_FD_STOP;
+                                       $this->FdRestartAction->Text = self::DEFAULT_ACTION_FD_RESTART;
+                               }
+
                                if ($this->config['api']['auth_type'] === 'basic') {
                                        // API basic auth data
                                        $this->AuthBasic->Checked = true;
@@ -169,7 +228,8 @@ class APIInstallWizard extends BaculumAPIPage {
                        'api' => array(),
                        'db' => array(),
                        'bconsole' => array(),
-                       'jsontools' => array()
+                       'jsontools' => array(),
+                       'actions' => array()
                );
                if ($this->AuthBasic->Checked) {
                        $cfg_data['api']['auth_type'] =  'basic';
@@ -201,6 +261,17 @@ class APIInstallWizard extends BaculumAPIPage {
                $cfg_data['jsontools']['fd_cfg_path'] = $this->FdCfgPath->Text;
                $cfg_data['jsontools']['bbconsjson_path'] = $this->BBconsJSONPath->Text;
                $cfg_data['jsontools']['bcons_cfg_path'] = $this->BconsCfgPath->Text;
+               $cfg_data['actions']['enabled'] = (integer)($this->ActionsYes->Checked === true);
+               $cfg_data['actions']['use_sudo'] = (integer)($this->ActionsUseSudo->Checked === true);
+               $cfg_data['actions']['dir_start'] = $this->DirStartAction->Text;
+               $cfg_data['actions']['dir_stop'] = $this->DirStopAction->Text;
+               $cfg_data['actions']['dir_restart'] = $this->DirRestartAction->Text;
+               $cfg_data['actions']['sd_start'] = $this->SdStartAction->Text;
+               $cfg_data['actions']['sd_stop'] = $this->SdStopAction->Text;
+               $cfg_data['actions']['sd_restart'] = $this->SdRestartAction->Text;
+               $cfg_data['actions']['fd_start'] = $this->FdStartAction->Text;
+               $cfg_data['actions']['fd_stop'] = $this->FdStopAction->Text;
+               $cfg_data['actions']['fd_restart'] = $this->FdRestartAction->Text;
 
                $ret = $this->getModule('api_config')->setConfig($cfg_data);
                if ($ret) {
@@ -426,5 +497,23 @@ class APIInstallWizard extends BaculumAPIPage {
                }
                $param->setIsValid($valid);
        }
+
+       public function testExecActionCommand($sender, $param) {
+               $action = $param->CommandParameter;
+               $cmd = '';
+               switch ($action) {
+                       case 'dir_start': $cmd = $this->DirStartAction->Text; break;
+                       case 'dir_stop': $cmd = $this->DirStopAction->Text; break;
+                       case 'dir_restart': $cmd = $this->DirRestartAction->Text; break;
+                       case 'sd_start': $cmd = $this->SdStartAction->Text; break;
+                       case 'sd_stop': $cmd = $this->SdStopAction->Text; break;
+                       case 'sd_restart': $cmd = $this->SdRestartAction->Text; break;
+                       case 'fd_start': $cmd = $this->FdStartAction->Text; break;
+                       case 'fd_stop': $cmd = $this->FdStopAction->Text; break;
+                       case 'fd_restart': $cmd = $this->FdRestartAction->Text; break;
+               };
+               $result = $this->getModule('comp_actions')->execCommand($cmd, $this->ActionsUseSudo->Checked);
+               $this->getCallbackClient()->callClientFunction('set_action_command_output', array($action, (array)$result));
+       }
 }
 ?>
index d20e0d4418e85cd23bbc8685de87f835ad02f11e..65e2c34a2c9c28bc8c99a74043744ebb94d7e0b8 100644 (file)
@@ -39,6 +39,8 @@
                <module id="status_dir" class="Application.API.Class.StatusDirector" />
                <module id="status_sd" class="Application.API.Class.StatusStorage" />
                <module id="status_fd" class="Application.API.Class.StatusClient" />
+               <!-- component actions modules -->
+               <module id="comp_actions" class="Application.API.Class.ComponentActions" />
                <!-- misc modules -->
                <module id="ls" class="Application.API.Class.Ls" />
        </modules>
index 45ee61ab1f5a00fce31328d25a9dcf646086b248..d1cc5721b776f006a852e9ac9b6734c74812a0b9 100644 (file)
@@ -91,6 +91,8 @@
        <url ServiceParameter="API.Config" pattern="api/v1/config/{component_type}/{resource_type}/{resource_name}/" parameters.component_type="[a-z]+" parameters.resource_type="[a-zA-Z]+" parameters.resource_name="[a-zA-Z0-9:.\-_ ]+" />
        <!-- component status endpoints -->
        <url ServiceParameter="API.ComponentStatus" pattern="api/v1/status/{component}/" parameters.component="(director|storage|client)" />
+       <!-- actions endpoints -->
+       <url ServiceParameter="API.Actions" pattern="api/v1/actions/{component}/{action}/" parameters.component="(director|storage|client)" parameters.action="(start|stop|restart)" />
 
 
 
index 53d2f4ee23644c15907c3db124eeb99bbc07226d..de9a5a9d5d802ca295c6dfc88f9bf0cf5b786abe 100644 (file)
@@ -53,10 +53,10 @@ class BconsoleError extends GenericError {
 
 class AuthorizationError extends GenericError {
 
-       const ERROR_AUTHORIZATION_TO_API_PROBLEM = 6;
+       const ERROR_AUTHENTICATION_TO_API_PROBLEM = 6;
        const ERROR_ACCESS_ATTEMPT_TO_NOT_ALLOWED_RESOURCE = 7;
 
-       const MSG_ERROR_AUTHORIZATION_TO_API_PROBLEM = 'Problem with authorization to Baculum API.';
+       const MSG_ERROR_AUTHENTICATION_TO_API_PROBLEM = 'Problem with authentication to Baculum API.';
        const MSG_ERROR_ACCESS_ATTEMPT_TO_NOT_ALLOWED_RESOURCE = 'Access attempt to not allowed resource. Permission denied.';
 }
 
@@ -177,4 +177,18 @@ class ConnectionError extends GenericError {
 
        const MSG_ERROR_CONNECTION_TO_HOST_PROBLEM = 'Problem with connection to remote host.';
 }
+
+class ActionsError extends GenericError {
+
+       const ERROR_ACTIONS_ACTION_DOES_NOT_EXIST = 110;
+       const ERROR_ACTIONS_DISABLED = 111;
+       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.';
+}
 ?>
index 9112bf001fa6009db71efa170d6ef237ae969bf4..e3654396a9157861091877971568972d066f9819 100644 (file)
@@ -87,10 +87,26 @@ class Miscellaneous extends TModule {
        private $runningJobStates = array('C', 'R');
 
        private $components = array(
-               'dir' => array('full_name' => 'Director', 'main_resource' => 'Director'),
-               'sd' => array('full_name' => 'Storage Daemon', 'main_resource' => 'Storage'),
-               'fd' => array('full_name' => 'File Daemon', 'main_resource' => 'FileDaemon'),
-               'bcons' => array('full_name' => 'Console', 'main_resource' => 'Director')
+               'dir' => array(
+                       'full_name' => 'Director',
+                       'url_name' => 'director',
+                       'main_resource' => 'Director'
+               ),
+               'sd' => array(
+                       'full_name' => 'Storage Daemon',
+                       'url_name' => 'storage',
+                       'main_resource' => 'Storage'
+               ),
+               'fd' => array(
+                       'full_name' => 'File Daemon',
+                       'url_name' => 'client',
+                       'main_resource' => 'FileDaemon'
+               ),
+               'bcons' => array(
+                       'full_name' => 'Console',
+                       'url_name' => 'console',
+                       'main_resource' => 'Director'
+               )
        );
 
        private $replace_opts = array(
@@ -143,6 +159,14 @@ class Miscellaneous extends TModule {
                return $name;
        }
 
+       public function getComponentUrlName($type) {
+               $name = '';
+               if (key_exists($type, $this->components)) {
+                       $name = $this->components[$type]['url_name'];
+               }
+               return $name;
+       }
+
        public function getJobStatesByType($type) {
                $statesByType = array();
                $states = array();
index bf76b40bb04fd6c8b26ca6f12bfe265afe8664c1..f32cce2c06852d195cca72522ce2eeb10c3af494 100644 (file)
@@ -311,18 +311,41 @@ h4 {
        width: 590px;
        float: left;
 }
-
+.actions_lines {
+       width: 743px;
+}
 
 .config_lines div.field {
        width: 280px !important;
 }
 
-#config_fields {
+.actions_lines div.field {
+       width: 332px !important;
+}
+
+#config_fields, #actions_fields {
        width: 732px;
        margin: 0 auto;
        padding-top: 7px;
 }
 
+#actions_fields {
+       width: 810px;
+}
+
+#actions_fields .button {
+       display: inline-block;
+       width: 50px;
+}
+
+#actions_fields .button input[type="submit"] {
+       min-width: 60px;
+}
+
+#actions_fields .action_test_loader, #actions_fields .action_success, #actions_fields .action_error {
+       margin-left: 11px;
+}
+
 #config_fields div.button {
        padding-left: 247px;
 }
@@ -331,7 +354,11 @@ fieldset.config_field {
        width: 667px;
 }
 
-fieldset.config_field .line {
+fieldset.actions_field {
+       width: 745px;
+}
+
+fieldset.config_field .line, fieldset.actions_field .line {
        height: auto;
        min-height: 35px !important;
        padding-top: 2px;