diff --git a/demo/public/locales/cs/translation.json b/demo/public/locales/cs/translation.json
index e94765064..65bf6e1b4 100644
--- a/demo/public/locales/cs/translation.json
+++ b/demo/public/locales/cs/translation.json
@@ -22,62 +22,6 @@
"HelpButton": {
"Help": "Nápověda"
},
- "ASABConfig": {
- "Nothing has been selected": "Zatím nebylo nic vybráno",
- "Please select the configuration from tree menu on the left side of the screen": "Prosím vyberte konfiguraci z nabídky na levé straně obrazovky",
- "Save": "Uložit",
- "Read only": "Pouze pro čtení",
- "Basic": "Základní nastavení",
- "Advanced": "Pokročilé nastavení",
- "Data updated successfully": "Data úspěšně aktualizována",
- "Something went wrong, failed to update data": "Něco je špatně, nepodařilo se aktualizovat data",
- "Something went wrong": "Něco je špatně!",
- "We are sorry, but the file cannot be found": "Omlouváme se, ale soubor nebylo možné najít :-(",
- "Config file does not exist": "Konfigurace nebyla nalezena",
- "Something went wrong! Unable to get schema": "Něco je špatně! Nepodařilo se načíst schema {{type}}",
- "Unable to get data for tree menu": "Nepodařilo se načíst data pro tree menu",
- "Unable to get schema. Try to reload the page": "Nepodařilo se načíst schema {{type}}. Zkuste obnovit stránku",
- "Unable to get config data. Try to reload the page": "Nepodařilo se načíst {{config}} data. Zkuste obnovit stránku",
- "Configuration name can't be empty!": "Název konfigurace nemůže být prázdný!",
- "Remove": "Odstranit",
- "Something went wrong, failed to create configuration": "Něco je špatně, nepodařilo se vytvořit konfiguraci",
- "Configuration created successfully": "Konfigurace vytvořena úspěšně",
- "Name": "Název",
- "Configuration name": "Název konfigurace",
- "Fill out configuration name": "Vyplňte název konfigurace",
- "Create": "Vytvořit",
- "New": "Nová",
- "Schema title": "Název schema",
- "Schema description": "Popis schema",
- "Something went wrong! Unable to get configurations": "Něco je špatně! Nepodařilo se získat konfiguraci pro {{config}}",
- "Unable to get configurations. Try to reload the page": "Nepodařilo se získat konfiguraci pro {{config}}. Zkuste obnovit stránku",
- "Create configuration": "Vytvořit konfiguraci",
- "Do you want to remove this configuration?": "Opravdu chcete odstranit tuto konfiguraci?",
- "Something went wrong, failed to remove configuration": "Něco je špatně, nepodařilo se odstranit konfiguraci",
- "New configuration": "Nová konfigurace",
- "Section added": "Sekce přidána",
- "Add new section": "Přidat novou sekci",
- "Add": "Přidat",
- "Type": "Typ",
- "Do you want to remove this section?": "Opravdu chcete odstranit tuto sekci?",
- "Actions": "Akce",
- "Export": "Exportovat",
- "Import": "Importovat",
- "Back": "Zpět",
- "Only tar.gz files are allowed": "Povoleny jsou pouze soubory tar.gz",
- "Search": "Hledat",
- "Override": "Přepsat",
- "Merge": "Spojit",
- "Import configuration": "Importovat konfiguraci",
- "Choose file": "Vyberte soubor",
- "No file chosen": "Nebyl vybrán žádný soubor",
- "Failed to import configuration": "Nepodařilo se importovat konfiguraci",
- "Configuration has been successfully imported": "Konfigurace byla úspěšně importována"
- },
- "ASABConfigModule": {
- "Actions": "Akce",
- "Search": "Hledat"
- },
"ASABConfigService": {
"Incorrect/invalid config file downloaded": "Došlo ke stažení neplatné konfigurace",
"Error when downloading a config file. The path might be corrupted": "Chyba při stahování kofigurace. Cesta k souboru nemusí být správně"
diff --git a/demo/public/locales/en/translation.json b/demo/public/locales/en/translation.json
index f57b69df5..574dabf65 100644
--- a/demo/public/locales/en/translation.json
+++ b/demo/public/locales/en/translation.json
@@ -22,60 +22,6 @@
"HelpButton": {
"Help": "Help"
},
- "ASABConfig": {
- "Nothing has been selected": "Nothing has been selected",
- "Please select the configuration from tree menu on the left side of the screen": "Please select the configuration from tree menu on the left side of the screen",
- "Save": "Save",
- "Read only": "Read only",
- "Basic": "Basic",
- "Advanced": "Advanced",
- "Data updated successfully": "Data updated successfully",
- "Something went wrong, failed to update data": "Something went wrong, failed to update data",
- "Something went wrong": "Something went wrong!",
- "We are sorry, but the file cannot be found": "We are sorry, but the file cannot be found :-(",
- "Config file does not exist": "Config file does not exist",
- "Something went wrong! Unable to get schema": "Something went wrong! Unable to get schema {{type}}",
- "Unable to get data for tree menu": "Unable to get data for tree menu",
- "Unable to get schema. Try to reload the page": "Unable to get schema {{type}}. Try to reload the page",
- "Unable to get config data. Try to reload the page": "Unable to get config {{config}} data. Try to reload the page",
- "Configuration name can't be empty!": "Configuration name can't be empty!",
- "Remove": "Remove",
- "Something went wrong, failed to create configuration": "Something went wrong, failed to create configuration",
- "Configuration created successfully": "Configuration created successfully",
- "Name": "Name",
- "Configuration name": "Configuration name",
- "Fill out configuration name": "Fill out configuration name",
- "Create": "Create",
- "New": "New",
- "Schema title": "Schema title",
- "Schema description": "Schema description",
- "Something went wrong! Unable to get configurations": "Something went wrong! Unable to get configurations for {{config}}",
- "Unable to get configurations. Try to reload the page": "Unable to get configurations for {{config}}. Try to reload the page",
- "Create configuration": "Create configuration",
- "Do you want to remove this configuration?": "Do you want to remove this configuration?",
- "Something went wrong, failed to remove configuration": "Something went wrong, failed to remove configuration",
- "New configuration": "New configuration",
- "Section added": "Section added",
- "Add new section": "Add new section",
- "Add": "Add",
- "Type": "Type",
- "Do you want to remove this section?": "Do you want to remove this section?",
- "Actions": "Actions",
- "Export": "Export",
- "Import": "Import",
- "Back": "Back",
- "Only tar.gz files are allowed": "Only tar.gz files are allowed",
- "Search": "Search",
- "Override": "Override",
- "Merge": "Merge",
- "Import configuration": "Import configuration",
- "Failed to import library": "Failed to import library",
- "Configuration has been successfully imported": "Configuration has been successfully imported"
- },
- "ASABConfigModule": {
- "Actions": "Actions",
- "Search": "Search"
- },
"ASABConfigService": {
"Incorrect/invalid config file downloaded": "Incorrect/invalid config file downloaded",
"Error when downloading a config file. The path might be corrupted": "Error when downloading a config file. The path might be corrupted"
diff --git a/doc/asab-config.md b/doc/asab-config.md
deleted file mode 100644
index c7b2a820d..000000000
--- a/doc/asab-config.md
+++ /dev/null
@@ -1,519 +0,0 @@
-# ASAB Config
-
-## Setup
-
-Before using this component in your project, `react-simple-tree-menu` and `react-hook-form - v7` must be installed and added into the project's `package.json` file:
-
-```
-yarn add react-simple-tree-menu
-yarn add react-hook-form
-```
-
-In `config` file, define ASAB Config as a service:
-
-```
-module.exports = {
- app: {
-
- ...
-
- },
- webpackDevServer: {
- port: 3000,
- proxy: {
- '/api/asab_config': {
- target: 'http://localhost:8082',
- pathRewrite: {'^/api/asab_config' : ''},
- ws: true
- },
- }
- }
-}
-```
-
-In the top-level `index.js` of your ASAB UI application, load the ASAB config module
-
-```
-const modules = [];
-
-...
-
-import ASABConfigModule from 'asab-webui/modules/maintenance/ConfigModule';
-modules.push(ASABConfigModule);
-
-...
-
-ReactDOM.render((
-
-
-
-), document.getElementById('app'));
-```
-
-The module will be displayed as a subitem of `Maintenance` in the sidebar navigation.
-
-
-## Authorization (resources)
-
-To provide any of these actions - `Create`, `Remove`, `Save`, `Import`, `Export` - configuration, one needs to have `config:admin` and/or `authz:superuser` resource.
-
-## Schema and configuration files
-
-To obtain / save configuration, ASAB config service must be running.
-
-Configuration and schema has to be saved in Zookeeper.
-
-Config structure in Zookeeper should be set as following:
-
-```
-- **main Zookeeper node**
- - config
- - **type**
- - **config**
- - type
- - **type**
- - schema
-```
-
-where
-
-- **main Zookeeper node** is the main node in the Zookeeper
-- **type** is the name of the section
-- **config** is the name of the configuration
-
-Configuration, which will not fit the schema will fall into adHoc value/section, which is strictly read-only.
-
-
-### Basic config and schema example for Section properties
-
-Config example:
-
-```
-{
- "my-source": {
- "source": "my-data-source*",
- "datetime_field": "@timestamp",
- "type": "elasticsearch"
- }
-}
-```
-
-Schema example:
-
-```
-{
- "$id": "Props schema",
- "type": "object",
- "title": "Props schema",
- "description": "My props schema",
- "default": {},
- "examples": [
- {
- "My:source": {
- "source": "my-data-source*",
- "datetime_field": "@timestamp",
- "type": "elasticsearch"
- }
- }
- ],
- "required": ["my-source"], // Required value is optional for properties
- "properties": {
- "My:source": {
- "type": "string",
- "title": "Some source",
- "description": "My Some source",
- "default": {},
- "examples": [
- {
- "source": "my-data-source*",
- "datetime_field": "@timestamp",
- "type": "elasticsearch"
- }
- ],
- "required": [
- "source",
- "datetime_field",
- "type"
- ],
- "properties": {
- "source": {
- "type": "string",
- "title": "Data source",
- "description": "Value for Data source",
- "default": "",
- "examples": [
- "my-data-source*"
- ]
- },
- "datetime_field": {
- "type": "string",
- "title": "Datetime",
- "description": "Datetime value",
- "default": "",
- "examples": [
- "@timestamp"
- ]
- },
- "type": {
- "type": "string",
- "title": "Type",
- "description": "Select type",
- "default": ["elasticsearch", "api"],
- "$defs": {
- "select": { "type": "select" }
- },
- "examples": [
- "elasticsearch"
- ]
- }
- },
- "additionalProperties": false
- }
- },
- "additionalProperties": false
-}
-```
-
-### Basic schema example for Section pattern properties
-
-Config example:
-
-```
-{
- "Some:source:my-source": {
- "source": "my-data-source*",
- "datetime_field": "@timestamp",
- "type": "elasticsearch"
- }
-}
-```
-
-Schema example:
-
-```
-{
- "$id": "Pattern props schema",
- "type": "object",
- "title": "Pattern props schema",
- "description": "My pattern props schema",
- "default": {},
- "examples": [
- {
- "Some:source:my-source": {
- "source": "my-data-source*",
- "datetime_field": "@timestamp",
- "type": "elasticsearch"
- }
- }
- ],
- "required": [], // For pattern properties section, required should be left empty
- "patternProperties": {
- "^Some:source:.*$": {
- "type": "string",
- "title": "Some source",
- "description": "My Some source",
- "default": {},
- "examples": [
- {
- "source": "my-data-source*",
- "datetime_field": "@timestamp",
- "type": "elasticsearch"
- }
- ],
- "required": [
- "source",
- "datetime_field",
- "type"
- ],
- "properties": {
- "source": {
- "type": "string",
- "title": "Data source",
- "description": "Value for Data source",
- "default": "",
- "examples": [
- "my-data-source*"
- ]
- },
- "datetime_field": {
- "type": "string",
- "title": "Datetime",
- "description": "Datetime value",
- "default": "",
- "examples": [
- "@timestamp"
- ]
- },
- "type": {
- "type": "string",
- "title": "Type",
- "description": "Select type",
- "default": ["elasticsearch", "api"],
- "$defs": {
- "select": { "type": "select" }
- },
- "examples": [
- "elasticsearch"
- ]
- }
- },
- "additionalProperties": false
- }
- },
- "additionalProperties": false
-}
-```
-
-## Supported inputs types
-
-### String values
-
-#### Single string
-
-```
-"properties": {
- "name": {
- "type": "string",
- "title": "Name",
- "description": "Fill your name",
- "default": "",
- "examples": [
- "Your Name"
- ]
- }
-}
-```
-
-#### Password
-
-```
-"properties": {
- "email": {
- "type": "string",
- "title": "Password",
- "description": "Fill your secret",
- "default": "",
- "$defs": {
- "password": {
- "type": "password"
- }
- },
- "examples": [
- "S0meSecr3t"
- ]
- }
-}
-```
-
-#### Email
-
-```
-"properties": {
- "email": {
- "type": "string",
- "title": "Email",
- "description": "Fill your email",
- "default": "",
- "$defs": {
- "email": {
- "type": "email"
- }
- },
- "examples": [
- "your@email.ex"
- ]
- }
-}
-```
-
-#### URL
-
-```
-"properties": {
- "url": {
- "type": "string",
- "title": "URL",
- "description": "Fill the redirect URL",
- "default": "",
- "$defs": {
- "url": {
- "type": "url"
- }
- },
- "examples": [
- "http://my-url.my"
- ]
- }
-}
-```
-
-#### Text area
-
-```
-"properties": {
- "image": {
- "type": "string",
- "title": "Base64 image",
- "description": "Add base64 image string",
- "default": "",
- "$defs": {
- "textarea": {
- "type": "textarea"
- }
- },
- "examples": [
- "data:image/svg;base64,iVBORw0KG..."
- ]
- }
-}
-```
-
-#### Select
-
-```
-"properties": {
- "slct": {
- "type": "string",
- "title": "Select a string",
- "description": "Please select a string value",
- "default": ["string one", "string two"],
- "$defs": {
- "select": { "type": "select" }
- },
- "examples": [
- "string one"
- ]
- }
-}
-```
-
-### Number values
-
-#### Single number
-
-```
-"properties": {
- "nmbr": {
- "type": "number",
- "title": "Random number",
- "description": "Add any number",
- "default": "",
- "$defs": {
- "number": {
- "type": "number"
- }
- },
- "examples": [
- 666
- ]
- }
-}
-```
-
-#### Select
-
-```
-"properties": {
- "nmbrslct": {
- "type": "number",
- "title": "Select the number you like",
- "description": "Please select some number",
- "default": [1,2,3],
- "$defs": {
- "select": {
- "type": "select"
- }
- },
- "examples": [
- 1
- ]
- }
-}
-```
-
-### Boolean values
-
-Using checkbox
-
-```
-"properties": {
- "chckbx": {
- "type": "boolean",
- "title": "Turn on/off my checkbox",
- "description": "Turn me on/off",
- "default": true,
- "$defs": {
- "checkbox": {
- "type": "checkbox"
- }
- },
- "examples": [
- true
- ]
- }
-}
-```
-
-### Array values
-
-```
-"properties": {
- "arr": {
- "type": "array",
- "title": "Array values",
- "description": "Please write string values separated by comma",
- "default": "",
- "examples": [
- "string one", "string two"
- ]
- }
-}
-```
-
-## Language localisations
-
-Language localizations for ASAB-Config configuration can be added to the translation.json files of `public/locales/en` & `public/locales/cs` of the product where ASAB Config module is used.
-
-Example:
-
-```
-{
- "ASABConfig": {
- "Nothing has been selected": "Nothing has been selected",
- "Please select the configuration from tree menu on the left side of the screen": "Please select the configuration from tree menu on the left side of the screen",
- "Save": "Save",
- "Read only": "Read only",
- "Basic": "Basic",
- "Advanced": "Advanced",
- "Data updated successfully": "Data updated successfully",
- "Something went wrong, failed to update data": "Something went wrong, failed to update data",
- "Something went wrong": "Something went wrong!",
- "We are sorry, but the file cannot be found": "We are sorry, but the file cannot be found :-(",
- "Config file does not exist": "Config file does not exist",
- "Something went wrong! Unable to get schema": "Something went wrong! Unable to get schema {{type}}",
- "Unable to get data for tree menu": "Unable to get data for tree menu",
- "Unable to get schema. Try to reload the page": "Unable to get schema {{type}}. Try to reload the page",
- "Unable to get config data. Try to reload the page": "Unable to get config {{config}} data. Try to reload the page",
- "Configuration name can't be empty!": "Configuration name can't be empty!",
- "Remove": "Remove",
- "Something went wrong, failed to create configuration": "Something went wrong, failed to create configuration",
- "Configuration created successfully": "Configuration created successfully",
- "Name": "Name",
- "Configuration name": "Configuration name",
- "Fill out configuration name": "Fill out configuration name",
- "Create": "Create",
- "New": "New",
- "Something went wrong! Unable to get configurations": "Something went wrong! Unable to get configurations for {{config}}",
- "Unable to get configurations. Try to reload the page": "Unable to get configurations for {{config}}. Try to reload the page",
- "Create configuration": "Create configuration",
- "Do you want to remove this configuration?": "Do you want to remove this configuration?",
- "Something went wrong, failed to remove configuration": "Something went wrong, failed to remove configuration",
- "New configuration": "New configuration",
- "Section added": "Section added",
- "Add new section": "Add new section",
- "Add": "Add",
- "Type": "Type",
- "Do you want to remove this section?": "Do you want to remove this section?"
- }
-}
-```
diff --git a/doc/asab-services.md b/doc/asab-services.md
deleted file mode 100644
index 7f5226cee..000000000
--- a/doc/asab-services.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# ASAB Services
-
-ASAB WebUI Services is a page with a list of available instances. It use a websocket connection, so the data are propagated realtime.
-
-## Setup
-
-In `config` file, define ASAB Services as a service:
-
-```
-module.exports = {
- app: {
-
- ...
-
- },
- webpackDevServer: {
- port: 3000,
- proxy: {
- '/api/lmio_remote_control': {
- target: 'http://localhost:8086',
- ws: true,
- pathRewrite: {'^/api/lmio_remote_control' : ''}
- },
- }
- }
-}
-```
-
-In the top-level `index.js` of your ASAB UI application, load the ASAB services module
-
-```
-const modules = [];
-
-...
-
-import ASABServicesModule from 'asab-webui/modules/maintenance/ServicesModule';
-modules.push(ASABServicesModule);
-
-...
-
-ReactDOM.render((
-
-
-
-), document.getElementById('app'));
-```
-
-The module will be displayed as a subitem of `Maintenance` in the sidebar navigation.
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigContainer.js b/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigContainer.js
deleted file mode 100644
index 0fc17c23c..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigContainer.js
+++ /dev/null
@@ -1,239 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { useSelector, connect } from "react-redux";
-
-import {
- Container, Col, Row,
- Card, CardBody
-} from "reactstrap";
-
-import { useTranslation } from 'react-i18next';
-
-import { TreeViewComponent } from "./TreeViewComponent";
-import ConfigEditor from "./ConfigEditor";
-import ConfigList from "./ConfigList";
-import ConfigImport from "./ConfigImport";
-import { getBrandImage } from "asab-webui";
-
-function ConfigContainer(props) {
-
- const ASABConfigAPI = props.app.axiosCreate('asab_config');
- const serviceURL = props.app.getServiceURL('asab_config');
- const { t } = useTranslation();
-
- const configType = props.match.params.configType;
- const configName = props.match.params.configName;
-
- const theme = useSelector(state => state.theme);
- const homeScreenAlt = props.app.Config.get('title');
-
- const [ treeData, setTreeData ] = useState({}); // Set complete data for TreeViewComponent
- const [ createConfig, setCreateConfig ] = useState(false); // Use for condition to render components
- const [ chosenPanel, setChosenPanel ] = useState("configurator"); // Sets the condition for showing the ConfigImport component
- const [ typeList, setTypeList ] = useState([]); // Set data name of type for group configuration
- const [ treeList, setTreeList ] = useState({}); // Set cleaned data for trigger UseEffect for updating TreeViewComponent, and for render the tree
- const [ openNodes, setOpenNodes ] = useState([]); // Set open nodes in the TreeMenu
- const [ homeScreenImg, setHomeScreenImg ] = useState({}); // Set open nodes in the TreeMenu
-
- useEffect(() => {
- setHomeScreenImg(getBrandImage(props, theme));
- }, [theme]);
-
- // To get the full overview on schemas and configs it is needed to update the tree list and data state
- useEffect(() => {
- getTypes();
- }, []);
-
- useEffect(() => {
- if (typeList.length > 0) {
- getTree();
- }
- }, [typeList])
-
- useEffect(() => {
- getChart();
- }, [treeList]);
-
- // Obtain list of types
- // TODO: add Error Card screen when no types are fetched
- const getTypes = async () => {
- try {
- let response = await ASABConfigAPI.get("/type");
- if (response.data.result != 'OK') {
- throw new Error("Unable to get data for tree menu");
- }
- // Sort data
- let sortedData = response.data.data;
- sortedData = sortedData.sort();
- setTypeList(sortedData);
- // TODO: validate responses which are not 200
- }
- catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Unable to get data for tree menu")}. ${e?.response?.data?.message}`, 30);
- return;
- }
- }
-
- // Obtain the list of configs parsed to the type key
- const getTree = async () => {
- let tree = await Promise.all(typeList.map(t => getConfigs(t)));
- setTreeList(tree);
- }
-
-
- const getConfigs = async (typeId) => {
- let tree = {};
- try {
- let response = await ASABConfigAPI.get("/config/" + typeId);
- if (response.data.result == 'OK'){
- // Sort data
- let sortedData = response.data.data;
- if (sortedData != undefined) {
- sortedData = sortedData.sort();
- tree[typeId] = sortedData;
- }
- }
- return tree;
- }
- catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Unable to get schema. Try to reload the page", {type: typeId})}. ${e?.response?.data?.message}`, 30);
- return;
- }
- }
-
- // Handle treeList to obtain the structure to render the tree
- const getChart = () => {
- let dataChart = [];
- Object.values(treeList).map((element, idx) => {
- addTreeStructure(element, dataChart);
- });
- let nodes = [];
- dataChart.map(node => {
- nodes.push(node.key)
- });
- setOpenNodes(nodes);
- setTreeData(dataChart);
- }
-
-
- const addTreeStructure = (element, dataChart) => {
- if (typeof element === 'object' && element !== null) {
- Object.keys(element).map((key) => {
- var obj = {
- type: "folder", // this is not needed yet, but it might be useful for icons
- key: key,
- label: key,
- nodes: []
- };
- dataChart.push(obj);
-
- var index = dataChart.indexOf(obj);
- if (element[key] != undefined) {
- element[key].map((e) => {
- if (typeof e === "object" && e !== null) {
- addTreeStructure(e, dataChart[index].nodes);
- } else if (typeof e === "string" && e !== null) {
- var strObj = {
- type: "file", // this is not needed yet, but it might be useful for icons
- key: e,
- label: e
- };
- dataChart[index].nodes.push(strObj);
- }
- })
- }
- })
- } else if (element !== undefined) {
- dataChart.push(
- {
- type: "file", // this is not needed yet, but it might be useful for icons
- key: element,
- label: element,
- }
- );
- }
- }
-
-
- // Render function
- return (
-
-
-
-
-
-
- {chosenPanel != 'import' ?
- configType != '$' && configName != '$' ?
- configName != '!manage' && createConfig == false ?
-
- :
-
- :
-
-
-
-
-
-
-
-
- {t('ASABConfig|Nothing has been selected')}
-
-
- {t('ASABConfig|Please select the configuration from tree menu on the left side of the screen')}
-
-
-
-
-
- :
-
- }
-
-
-
- )
-}
-
-function mapStateToProps(state) {
- return {
- config_created: state.asab_config.config_created,
- config_removed: state.asab_config.config_removed,
- config_imported: state.asab_config.config_imported
- }
-}
-
-export default connect(mapStateToProps)(ConfigContainer);
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigEditor.js b/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigEditor.js
deleted file mode 100644
index d36dbc704..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigEditor.js
+++ /dev/null
@@ -1,776 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { useForm } from "react-hook-form";
-import ReactJson from 'react-json-view';
-import classnames from 'classnames';
-import { useTranslation } from 'react-i18next';
-import { useHistory } from "react-router-dom";
-import { useSelector } from 'react-redux';
-
-import {
- Card, CardBody, CardHeader, CardFooter,
- Form, FormGroup, FormText, Input, Label,
- TabContent, TabPane, Nav, NavItem, NavLink,
- Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
- Row, Col, ButtonGroup
-} from "reactstrap";
-
-import {
- NumberConfigItem,
- CheckBoxConfigItem,
- ConfigAdHocItem,
- StringItems
-} from './ConfigFormatItems';
-
-import { ButtonWithAuthz, getBrandImage } from 'asab-webui';
-
-function ConfigEditor(props) {
- const { register, handleSubmit, setValue, getValues, formState: { errors, isSubmitting }, reset, resetField } = useForm();
- const { t, i18n } = useTranslation();
- const ASABConfigAPI = props.app.axiosCreate('asab_config');
- let history = useHistory();
-
- const [ adHocValues, setAdHocValues ] = useState({});
- const [ adHocSections, setAdHocSections ] = useState({});
- const [ formStruct, setFormStruct ] = useState({});
- const [ jsonValues, setJsonValues ] = useState({});
-
- // States for schema sections
- const [ selectPatternSections, setSelectPatternSections ] = useState([]);
- const [ patternPropsSchema, setPatternPropsSchema ] = useState({});
-
- // Retrieve the asab config url from config file
- const homeScreenAlt = props.app.Config.get('title');
- const configType = props.configType;
- const configName = props.configName;
-
- const [ configNotExist, setConfigNotExist ] = useState(false);
- const [ activeTab, setActiveTab ] = useState('basic');
-
- const resourceManageConfig = "asab:config:edit";
- const resources = useSelector(state => state.auth?.resources);
- const theme = useSelector(state => state?.theme);
-
- // Pattern props dropdown
- const [dropdownOpen, setDropdownOpen] = useState(false);
- const toggleDropDown = () => setDropdownOpen(!dropdownOpen);
-
- // Branding
- const [ homeScreenImg, setHomeScreenImg ] = useState({});
-
- useEffect(() => {
- setHomeScreenImg(getBrandImage(props, theme))
- }, [theme]);
-
- // The container will be re-rendered on configType or configName change
- useEffect(() => {
- initialLoad();
- }, [ configType, configName ]);
-
- // Set values based on form struct
- useEffect(() => {
- if (Object.keys(formStruct).length > 0) {
- setValues();
- }
- }, [formStruct])
-
- // Load data and set up the data for form struct
- const initialLoad = async () => {
- let values = undefined;
- let schema = undefined;
- setConfigNotExist(false);
- // Set selected pattern sections to empty
- setSelectPatternSections([]);
-
- let prevValues = getValues();
- (prevValues && Object.keys(prevValues).length > 0) && Object.keys(prevValues).map((key, idx) => {
- resetField(key);
- })
-
- try {
- let response = await ASABConfigAPI.get(`/type/${configType}`);
- // TODO: validate responses which are not 200
- if (response.data.result != 'OK') {
- throw new Error("Something went wrong! Unable to get schema")
- }
- schema = response.data.data;
- }
- catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Unable to get schema. Try to reload the page", {type: configType})}. ${e?.response?.data?.message}`, 30);
- return;
- }
-
- try {
- let response = await ASABConfigAPI.get(`/config/${configType}/${configName}?format=json`);
- if (response.data.result != "OK") {
- props.app.addAlert("warning", `${t("ASABConfig|Config file does not exist")}. ${e?.response?.data?.message}`, 30);
- setConfigNotExist(true);
- return;
- }
- values = response.data.data;
- // TODO: validate responses which are not 200
- }
- catch(e) {
- // Set the states to initial state on failed response to clear the inputs
- setFormStruct({});
- setAdHocValues({});
- setAdHocSections({});
- setJsonValues({});
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Unable to get config data. Try to reload the page", {type: configName})}. ${e?.response?.data?.message}`, 30);
- return;
- }
-
- let formStructure = {};
- let schemaProps = {};
- let sectionKeyData = {};
- let ahValues = {};
- let ahSections = values;
- // TODO: handle nested patternProperties (in items)
- // Check for properties of schema
- if (schema.properties) {
- schemaProps = schema.properties;
- await Promise.all(values && Object.keys(values).map(async (section, idx) => {
- await Promise.all(Object.keys(schema.properties).map(async (sectionName, id) => {
- if (section == sectionName) {
- let arrAHValues = [];
- await Promise.all(Object.keys(values[section]).map(async (key, id) => {
- sectionKeyData[`${section} ${key}`] = values[section][key];
-
- // Check for adHoc values in properties of section
- if (schemaProps[`${section}`].properties) {
- // Add empty string to keys, which are not present in configuration, but key is present in schema
- await Promise.all(Object.keys(schemaProps[`${section}`].properties).map((schemaKey, i) => {
- if (Object.keys(values[section]).indexOf(schemaKey) == -1) {
- sectionKeyData[`${section} ${schemaKey}`] = "";
- }
- }));
- // Check if key exist in schema and if not, add it to adHoc values
- if (schemaProps[`${section}`].properties[`${key}`] == undefined) {
- let v = {};
- v[key] = values[section][key];
- arrAHValues.push(v);
- ahValues[`${section}`] = arrAHValues;
- }
- }
- }));
- // Mutate adHoc section based on the matching sections
- // If section name match with schema, then remove it from adHoc sections (it is also removed for submit)
- ahSections = Object.assign({}, ahSections);
- delete ahSections[section];
- }
- }));
- }));
- }
-
- // TODO: handle nested patternProperties (in items)
- // Check for pattern properties of schema
- if (schema.patternProperties) {
- // Handle only pattern properties with data
- if (values && Object.keys(values).length > 0) {
- await Promise.all(values && Object.keys(values).map(async (section, idx) => {
- await Promise.all(Object.keys(schema.patternProperties).map(async (sectionName, id) => {
- // Check for matching section name
- if (section.match(sectionName) != null) {
- // If matched, then add schema to schema props
- schemaProps[`${section}`] = schema.patternProperties[sectionName];
- let arrAHValues = [];
- await Promise.all(Object.keys(values[section]).map(async (key, i) => {
- // Add data for section
- sectionKeyData[`${section} ${key}`] = values[section][key];
-
- // Check for adHoc values in properties of section
- if (schemaProps[`${section}`].properties) {
- // Add empty string to keys, which are not present in configuration, but key is present in schema
- await Promise.all(Object.keys(schemaProps[`${section}`].properties).map((schemaKey, i) => {
- if (Object.keys(values[section]).indexOf(schemaKey) == -1) {
- sectionKeyData[`${section} ${schemaKey}`] = "";
- }
- }));
- // Check if key exist in schema and if not, add it to adHoc values
- if (schemaProps[`${section}`].properties[`${key}`] == undefined) {
- let v = {};
- v[key] = values[section][key];
- arrAHValues.push(v);
- ahValues[`${section}`] = arrAHValues;
- }
- }
- }));
- // Mutate adHoc section based on the matching sections
- // If section name match with schema, then remove it from adHoc sections (it is also removed for submit)
- ahSections = Object.assign({}, ahSections);
- delete ahSections[section];
- }
- }));
- }));
- }
-
- // Trim pattern props section names and push it to array to use it in the selector for adding a new empty config section
- let patternSections = [];
- await Promise.all(Object.keys(schema.patternProperties).map((sectionName, idx) => {
- let sectionTrimmed = sectionName.substring(1).replace(":.*$", "");
- patternSections.push(sectionTrimmed);
- }))
- setSelectPatternSections(patternSections);
- setPatternPropsSchema(schema.patternProperties);
- }
-
- // Set values for JSON view
- setJsonValues(values);
- // Set data for adHoc sections
- setAdHocSections(ahSections);
- // Set data for adHoc values
- setAdHocValues(ahValues);
-
- // Assign sectionKeyData to form struct under data key
- formStructure["data"] = sectionKeyData;
- // Assign schema to form struct under properties key
- formStructure["properties"] = schemaProps;
- // Set data and properties for form struct
- setFormStruct(formStructure);
- }
-
-
- // Set values from form struct and adHoc sections
- const setValues = () => {
- // Reset old values before setting new values to prevent unintended data submitting
- reset({});
-
- /*
- Set empty values for empty configuration (it will remove old values
- from forms when config is completelly empty and which reset is not able
- to remove)
- */
- if (formStruct.properties && formStruct.data && Object.keys(formStruct.data).length == 0) {
- Object.keys(formStruct.properties).map((key, idx) => {
- if (formStruct.properties[key].properties) {
- Object.keys(formStruct.properties[key].properties).map((k, i) => {
- setValue(`${key} ${k}`, "");
- })
- }
- })
- }
-
- // Set values from form struct for registration and submitting
- if (formStruct.data) {
- Object.entries(formStruct.data).map((entry, idx) => {
- setValue(`${entry[0]}`, entry[1]);
- });
- }
- // Set adHoc sections for submitting
- if (adHocSections) {
- Object.keys(adHocSections).map((section, idx) => {
- Object.keys(adHocSections[section]).map((key, id) => {
- setValue(`${section} ${key}`, adHocSections[section][key]);
- });
- });
- }
- }
-
-
- // Parse data to JSON format, stringify it and save to config file
- const onSubmit = async (data) => {
- // Get parsed sections for submit
- let parsedSections = await getParsedSections(data);
-
- try {
- let response = await ASABConfigAPI.put(`/config/${configType}/${configName}`,
- JSON.parse(JSON.stringify(parsedSections)),
- { headers: {
- 'Content-Type': 'application/json'
- }
- }
- )
- if (response.data.result != "OK"){
- throw new Error(t('ASABConfig|Something went wrong, failed to update data'));
- }
- props.app.addAlert("success", t('ASABConfig|Data updated successfully'));
- initialLoad(); // Load the new data after saving
- }
- catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Something went wrong, failed to update data")}. ${e?.response?.data?.message}`, 30);
- initialLoad();
- return;
- }
- }
-
- // Swith between the tabs
- const toggle = tab => {
- if(activeTab !== tab) setActiveTab(tab);
- }
-
- // Function to convert value types from string
- function convertValueType(sectionValue, valueType) {
- /*
- Conversion based on
- https://json-schema.org/understanding-json-schema/reference/type.html
- */
- let value;
- // Check number type values
- if (valueType == "number" ||
- valueType == "integer" ||
- valueType == "float" ||
- valueType == "null" ||
- valueType == "boolean") {
- // If value is an empty string, then return undefined (to prevent parsing failures)
- if (sectionValue === "") {
- value = value;
- } else {
- value = JSON.parse(sectionValue);
- }
- }
- // Check for array type values
- else if (valueType == "array") {
- // If value is an empty string, then return undefined (to prevent parsing failures)
- if (sectionValue === "") {
- value = value;
- } else {
- value = sectionValue.toString().split(",");
- }
- }
- // Check for object type values
- else if (valueType == "object") {
- // If value is an empty string, then return undefined (to prevent parsing failures)
- if (sectionValue === "") {
- value = value;
- } else {
- value = JSON.parse(JSON.stringify(sectionValue));
- }
- }
- // If not match any of the types, return default (string). This apply also for adHoc values of sections
- else {
- value = sectionValue;
- }
- return value;
- }
-
- // Confirm message form for config section removal
- const removeSectionForm = (sectionTitle) => {
- var r = confirm(t("ASABConfig|Do you want to remove this section?"));
- if (r == true) {
- removeSection(sectionTitle);
- }
- }
-
- // TODO: Unify the code with onSubmit, that it does not repeat itself
- // Remove config section
- const removeSection = async (sectionTitle) => {
- let data = getValues();
- // Get parsed section for removal section
- let parsedSections = await getParsedSections(data);
-
- // Remove section out of data and save result
- delete parsedSections[sectionTitle]
-
- try {
- let response = await ASABConfigAPI.put(`/config/${configType}/${configName}`,
- JSON.parse(JSON.stringify(parsedSections)),
- { headers: {
- 'Content-Type': 'application/json'
- }
- }
- )
- if (response.data.result != "OK"){
- throw new Error(t('ASABConfig|Something went wrong, failed to update data'));
- }
- props.app.addAlert("success", t('ASABConfig|Data updated successfully'));
- initialLoad(); // Load the new data after saving
- }
- catch(e) {
- console.error(e);
- props.app.addAlert("warning", t("ASABConfig|Something went wrong, failed to update data", {error: e?.response?.data?.message}), 30);
- initialLoad();
- return;
- }
- }
-
- // Add new section out of pattern properties section list
- const addNewSection = async (selectedSection) => {
- let section = selectedSection;
- let properties = formStruct.properties;
- let cnt = 1;
- let selectedProperties = {};
- await Promise.all(Object.keys(properties).map((sectionName, idx) => {
- if (sectionName.match(section) != null) {
- cnt += 1;
- selectedProperties = properties[sectionName];
- }
- }))
-
- let formStructure = formStruct;
- if (cnt == 1) {
- // If section not present in the configuration, use schema obtained from the service
- await Promise.all(Object.keys(patternPropsSchema).map(async (sectionName, id) => {
- if (sectionName.match(section) != null) {
- formStructure["properties"][`${section}:${cnt}`] = patternPropsSchema[sectionName];
- }
- }))
- } else {
- // If section already present in the configuration, use its schema props
- let newSection = `${section}:${cnt}`;
- // Check if there is a section of the same name in the configuration
- await Promise.all(Object.keys(properties).map(async (sectionName, id) => {
- // If there is a section of the same name in the config, add a random string to new section
- if (sectionName === newSection) {
- let randomString = Math.random().toString(36).substr(2, 1);
- newSection = newSection + randomString;
- }
- }))
- formStructure["properties"][newSection] = selectedProperties;
- }
-
- props.app.addAlert("success", t('ASABConfig|Section added'));
- // Update form struct and call setValues function to load data
- setFormStruct(formStructure);
- setValues();
- }
-
- // Function for obtaining parsed sections
- const getParsedSections = async (data) => {
- // Get 'type' of the values (if defined) from the schema
- let formStructProperties = formStruct.properties;
- let sectionTypes = {};
- // Iterate through sections
- if (Object.keys(formStructProperties).length > 0) {
- await Promise.all(Object.keys(formStructProperties).map(async (sect, idx) => {
- let valueTypes = {};
- // Iterate through section keys
- await Promise.all(Object.keys(formStructProperties[sect]).length > 0 && Object.keys(formStructProperties[sect]).map(async (key, id) => {
- if (key === "properties") {
- // Iterate through key properties
- await Promise.all(Object.entries(formStructProperties[sect]["properties"]).map((entry, i) => {
- // If type of the value is undefined, then default is string
- valueTypes[entry[0]] = entry[1].type ? entry[1].type : "string";
- }));
- }
- }));
- sectionTypes[sect] = valueTypes;
- }));
- }
-
- let parsedSections = {};
- // TODO: Disable saving output from ReactJSONview component
- if (activeTab == 'advanced') {
- // If data are being submitted from JSON view, dont parse data to object
- parsedSections = jsonValues;
- } else {
- let splitKey = "";
- let sectionTitle = "";
- let sectionKey = "";
- let sectionValue = "";
- // Parse data to object
- await Promise.all(Object.keys(data).map((key, idx) => {
- splitKey = key.split(" ");
- sectionTitle = splitKey[0];
- sectionKey = splitKey[1];
- sectionValue = data[key];
- // Parsing
- let obj = {};
- if (sectionTypes[sectionTitle] == undefined) {
- // Values of adHoc sections
- obj[sectionKey] = sectionValue;
- parsedSections[sectionTitle] = {...parsedSections[sectionTitle], ...obj};
- } else {
- let valueType = sectionTypes[sectionTitle][sectionKey];
- obj[sectionKey] = convertValueType(sectionValue, valueType);
- parsedSections[sectionTitle] = {...parsedSections[sectionTitle], ...obj};
- }
- }));
- }
- return parsedSections;
- }
-
- // Convert pattern section name for Add button from technical name to Title of the section defined in schema
- const sectionNameString = (patternpropsSchema, patternSection) => {
- let patternKey = Object.keys(patternPropsSchema) ? Object.keys(patternPropsSchema).filter(key => key.match(patternSection)) : "";
- let returnString = patternSection;
- Object.keys(patternPropsSchema) && Object.keys(patternPropsSchema).map((key,idx) => {
- if (patternKey[0] == key) {
- returnString = patternPropsSchema[key]?.title ? patternPropsSchema[key]?.title : patternSection;
- }
- })
- return returnString;
- }
-
- if (configNotExist) {
- return (
-
- )
- }
-
- // TODO: add Content loader when available as a component in ASAB WebUI
- return (
-
-
-
- );
-}
-
-export default ConfigEditor;
-
-
-function ConfigSection(props) {
- const { t, i18n } = useTranslation();
- return (
-
-
-
-
-
- {props.section['title']}
-
-
-
-
- {props.selectPatternSections.length > 0 &&
- {props.removeSectionForm(props.sectionname)}}
- disabled={props.isSubmitting}
- resource={props.resourceManageConfig}
- resources={props.resources}
- >
-
-
- }
-
-
-
-
- {Object.keys(props.section.properties).map((item_name, idx) =>
- // Decide what type of config item to render based on format
- // TODO: Update also other RADIO and SELECT types
- {switch(props.section.properties[item_name]['type']){
- case 'string': return()
- case 'number': return()
- case 'integer': return()
- case 'boolean': return()
- default: return()
- }}
- )}
-
- {/* List all remaining key/values (aka AdHocValues) from a config as simple Config Item */}
- {Object.keys(props.adhocvalues).length > 0 && Object.keys(props.adhocvalues).map((value_name, idx) =>
- {return(props.sectionname == value_name ?
-
- : null
- )}
- )}
-
-
- );
-}
-
-
-function ConfigAdHocSection(props) {
- const { t, i18n } = useTranslation();
- let myid = props.sectionname;
- return (
-
-
-
- {myid}
-
- {Object.keys(props.values).length > 0 && Object.keys(props.values).map((key, idx) =>
- {
- return (
-
-
-
-
- {t('ASABConfig|Read only')}
-
-
- )}
- )}
-
- );
-}
-
-// This component returns a pre-defined messages
-function ConfigMessageCard(props) {
- return(
-
-
-
- {props.purposeTitle}
- {props.purposeSubtitle}
-
- )
-}
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigFormatItems.js b/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigFormatItems.js
deleted file mode 100644
index a88a299c7..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigFormatItems.js
+++ /dev/null
@@ -1,378 +0,0 @@
-import React from "react";
-
-import {
- Form, FormGroup, FormText, Input, Label
-} from "reactstrap";
-
-import { useTranslation } from 'react-i18next';
-
-// TODO: Different types of ConfigItem to cover formats such as "number", "boolean", checkbox, radiobox
-
-export function StringItems(props) {
- if (props.defs) {
- let def = Object.keys(props.defs)[0];
- if (def == 'url') {
- return (
- )
- } else if (def == 'email') {
- return (
- )
- } else if (def == 'password') {
- return (
- )
-
- } else if (def == 'textarea') {
- return (
- )
- } else if (def == 'select') {
- return (
- )
- } else {
- // If not defined as above or if `text`, then the text input is used
- return (
- )
- }
- } else {
- return (
- )
- }
-}
-
-export function ConfigItem(props) {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
-
- {props.item['description']}
-
-
- );
-}
-
-
-export function NumberConfigItem(props) {
- if (props.defs && Object.keys(props.defs)[0] == 'select') {
- return (
- )
- } else {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
-
- {props.item['description']}
-
-
- );
- }
-}
-
-
-export function UrlConfigItem(props) {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
-
- {props.item['description']}
-
-
- );
-}
-
-
-export function EmailConfigItem(props) {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
-
- {props.item['description']}
-
-
- );
-}
-
-
-export function PasswordConfigItem(props) {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
-
- {props.item['description']}
-
-
- );
-}
-
-
-export function CheckBoxConfigItem(props) {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
-
-
-
- {props.item['description']}
-
-
- );
-}
-
-// TODO: Implement and test radio button when there will be a case for it
-export function RadioButtonConfigItem(props) {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
-
-
-
-
-
-
- {props.item['description']}
-
-
-
- );
-}
-
-
-export function SelectConfigItem(props) {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
- {props.item['default'].length > 0 ? props.item['default'].map((val, idx) => {return(
-
- )}) : null}
-
-
- {props.item['description']}
-
-
- );
-}
-
-
-export function TextAreaConfigItem(props) {
- let myid = `${props.sectionname} ${props.itemname}`;
- const reg = props.register(myid);
- return (
-
-
-
-
- {props.item['description']}
-
-
- );
-}
-
-export function ConfigAdHocItem(props) {
- const { t, i18n } = useTranslation();
- let myid = props.valuename;
- return (
- props.values.length > 1 ?
- props.values.map(obj => {
- return(
-
-
-
-
- {t('ASABConfig|Read only')}
-
-
- )
- })
- :
-
-
-
-
-
- {t('ASABConfig|Read only')}
-
-
-
- );
-}
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigImport.js b/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigImport.js
deleted file mode 100644
index 8be82d628..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigImport.js
+++ /dev/null
@@ -1,165 +0,0 @@
-import React, { useState, useRef, useEffect } from 'react';
-import { useTranslation } from 'react-i18next';
-
-import {
- CardBody, CardHeader, CardFooter, Row, Col,
- Button, Input, Label,
- FormGroup, FormText, InputGroup,
- InputGroupText, Card, ButtonGroup
-} from 'reactstrap';
-import {types} from "./actions/actions";
-
-
-const ConfigImport = (props) => {
-
- const ASABConfigAPI = props.app.axiosCreate('asab_config');
- const { t } = useTranslation();
- const [chosenFilename, setChosenFilename] = useState("No file chosen");
- const [type, setType] = useState("merge");
- const [errors, setErrors] = useState(false);
- const inputFileRef = useRef(null)
- const formRef = useRef(null);
-
- useEffect(() => {
- if (props.configImported) {
- props.getTree();
- props.app.Store.dispatch({
- type: types.CONFIG_IMPORTED,
- config_imported: false
- });
- }
- }, [props.configImported])
-
- // Choose file, click simulation on reference of the another
- const chooseFile = () => {
- if (!inputFileRef.current) return;
-
- inputFileRef.current.click();
- }
- // File formatting
- const updateFilename = () => {
- if (!inputFileRef.current) return;
-
- // Get filename from input and remove path
- const filename = inputFileRef.current.value.replace(/.*[\/\\]/, '');
- setChosenFilename(filename);
-
- // Check if file is tar
- if (!filename.includes(".tar.gz")) {
- setErrors(true);
- } else {
- setErrors(false);
- }
- }
-
- const onTypeChange = (e) => setType(e.target.value);
-
- // Import PUT request
- const importConfiguration = async (event) => {
- event.preventDefault();
- try {
- const data = new FormData(formRef.current);
- const response = await ASABConfigAPI.put(`/import?type=${type}`, data, {
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- })
-
- if (response.data.result !== "OK") throw new Error(`Response result is ${response.data.result}. File has not been imported`);
- props.app.Store.dispatch({
- type: types.CONFIG_IMPORTED,
- config_imported: true
- });
- props.setChosenPanel("editor");
- props.app.addAlert("success", t("ASABConfig|Configuration has been successfully imported"));
- } catch (e) {
- console.error("Failed to import configuration\n", e);
- props.app.addAlert("warning", `${t("ASABConfig|Failed to import configuration")}. ${e?.response?.data?.message}`, 30);
- }
- }
-
- return (
-
-
-
- )
-}
-
-export default ConfigImport;
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigList.js b/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigList.js
deleted file mode 100644
index 5c587eb7e..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/ConfigList.js
+++ /dev/null
@@ -1,262 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { useForm } from "react-hook-form";
-import { useTranslation } from 'react-i18next';
-import { useHistory, Link } from "react-router-dom";
-import { useSelector } from 'react-redux';
-
-import {
- Button,
- Card, CardBody, CardHeader, CardFooter,
- Form, FormGroup, FormText, Input, Label
-} from "reactstrap";
-
-import {types} from './actions/actions';
-
-import { DataTable, ButtonWithAuthz } from 'asab-webui';
-
-function ConfigList(props) {
- const { register, handleSubmit, getValues, formState: { errors, isSubmitting }, reset } = useForm();
- const { t, i18n } = useTranslation();
- const ASABConfigAPI = props.app.axiosCreate('asab_config');
- let history = useHistory();
-
- const [ configList, setConfigList ] = useState([]);
- const [ description, setDescription ] = useState("");
-
- const resourceManageConfig = "asab:config:edit";
- const resources = useSelector(state => state.auth?.resources);
-
- const configType = props.configType;
-
- const regConfigName = register("configName",
- {
- validate: {
- emptyInput: value => (getValues("configName") !== "" || t("ASABConfig|Configuration name can't be empty!")),
- }
- });
-
- // The container will be re-rendered on configType or configName change
- useEffect(() => {
- initialLoad();
- }, [ configType ]);
-
-
- const headers = [
- {
- name: t('ASABConfig|Name'),
- key: "name",
- link: { pathname: `/config/${configType}/`, key: "name" }
- },
- {
- name: ' ',
- customComponent: {
- generate: (obj) => (
-
- {removeConfigForm(obj.name), e.preventDefault()}}
- >
-
-
-
- )
- }
- }
-
- ];
-
-
- // Load data and set up the data for form struct
- const initialLoad = async () => {
- props.setCreateConfig(false);
- let data = [];
- let schema = {};
- try {
- let response = await ASABConfigAPI.get(`/config/${configType}`);
- // TODO: validate responses which are not 200
- if (response.data.result != 'OK') {
- throw new Error(t(`ASABConfig|Something went wrong! Unable to get configurations`, {config: configType}));
- }
- data = response.data.data;
- }
- catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Unable to get configurations. Try to reload the page", {config: configType})}. ${e?.response?.data?.message}`, 30);
- }
-
- try {
- let response = await ASABConfigAPI.get(`/type/${configType}`);
- // TODO: validate responses which are not 200
- if (response.data.result != 'OK') {
- throw new Error(t(`ASABConfig|Something went wrong! Unable to get schema`, {type: configType}));
- }
- schema = response.data.data;
- }
- catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Unable to get schema. Try to reload the page", {type: configType})}. ${e?.response?.data?.message}`, 30);
- }
-
- // Create an array of objects with name, schema title and schema description
- let cfgList = [];
- Promise.all(await data.map(cfg => {
- cfgList.push({name: cfg});
- }));
- setConfigList(cfgList);
- setDescription(schema?.description ? schema.description : "")
- }
-
- // Create configuration button
- const createConfigComponent = (
- {props.setCreateConfig(true), e.preventDefault()}}
- resource={resourceManageConfig}
- resources={resources}
- >
- {t("ASABConfig|Create")}
-
- );
-
- // Confirm message form for configuration removal
- const removeConfigForm = (configName) => {
- var r = confirm(t("ASABConfig|Do you want to remove this configuration?"));
- if (r == true) {
- removeConfig(configName);
- }
- }
-
- // Remove configuration
- const removeConfig = async (configName) => {
- try {
- let response = await ASABConfigAPI.delete(`/config/${configType}/${configName}`);
- if (response.data.result != "OK"){
- throw new Error(t('ASABConfig|Something went wrong, failed to remove configuration'));
- }
- props.app.Store.dispatch({
- type: types.CONFIG_REMOVED,
- config_removed: true
- });
- initialLoad();
- } catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Something went wrong, failed to remove configuration")}. ${e?.response?.data?.message}`, 30);
- }
- }
-
- if (props.createConfig) {
- return
- }
-
- return (
-
- {`${description}`}
}
- title={{ text: t("ASABConfig|Type") + ` ${configType}`, icon: "cil-settings" }}
- headers={headers}
- data={configList}
- limit={99999}
- customComponent={createConfigComponent}
- />
-
- )
-}
-
-export default ConfigList;
-
-
-function CreateConfigCard(props) {
- const { register, handleSubmit, getValues, formState: { errors, isSubmitting }, reset } = useForm();
- const { t, i18n } = useTranslation();
- const ASABConfigAPI = props.app.axiosCreate('asab_config');
- let history = useHistory();
-
- const regConfigName = register("configName",
- {
- validate: {
- emptyInput: value => (getValues("configName") !== "" || t("ASABConfig|Configuration name can't be empty!")),
- }
- });
-
- // Parse data to JSON format, stringify it and save to config file
- const onSubmit = async (data) => {
- let configName = data.configName;
- let configNameExtension = configName.split('.').pop();
-
- if (configName == configNameExtension) {
- configName = `${configName}.json`
- }
-
- try {
- let response = await ASABConfigAPI.put(`/config/${props.configType}/${configName}`,
- {},
- { headers: {
- 'Content-Type': 'application/json'
- }
- }
- )
- if (response.data.result != "OK"){
- throw new Error(t('ASABConfig|Something went wrong, failed to create configuration'));
- }
- props.setCreateConfig(false);
- props.app.addAlert("success", t('ASABConfig|Configuration created successfully'));
- props.app.Store.dispatch({
- type: types.CONFIG_CREATED,
- config_created: true
- });
- history.push({
- pathname: `/config/${props.configType}/${configName}`
- });
- }
- catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ASABConfig|Something went wrong, failed to create configuration")}. ${e?.response?.data?.message}`, 30);
- }
- }
-
- return(
-
- )
-}
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/TreeViewComponent.js b/src/modules/maintenance/ConfigModule/ConfigContainers/TreeViewComponent.js
deleted file mode 100644
index 2985d4e52..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/TreeViewComponent.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { useHistory } from "react-router-dom";
-import { useSelector } from 'react-redux';
-import { useTranslation } from 'react-i18next';
-
-import { TreeMenu } from 'asab-webui';
-import { types } from './actions/actions';
-import {
- Input,
- InputGroup, InputGroupText,
- ButtonDropdown, DropdownToggle,
- DropdownMenu, DropdownItem
-} from "reactstrap";
-
-
-export function TreeViewComponent(props) {
- const setChosenPanel = props.setChosenPanel;
-
- let history = useHistory();
- const { t, i18n } = useTranslation();
-
- const [isDropdownMenuOpen, setDropdownMenu] = useState(false);
-
- // Obtain resources from state (if available)
- const resources = useSelector(state => state.auth?.resources);
- const resource = "asab:config:edit";
-
- useEffect(() => {
- if (props.configCreated || props.configRemoved) {
- props.getTree();
- if (props.configCreated) {
- props.app.Store.dispatch({
- type: types.CONFIG_CREATED,
- config_created: false
- });
- }
- if (props.configRemoved) {
- props.app.Store.dispatch({
- type: types.CONFIG_REMOVED,
- config_removed: false
- });
- }
- }
- }, [props.configCreated, props.configRemoved]);
-
-
- // Get the configType and configName from the TreeView menu
- const onClickItem = ({ key }) => {
- // TODO: Update for multilevel tree structure
- let splitKey = key.split("/");
- props.setCreateConfig(false);
- if (splitKey.length > 1) {
- // Push params to the URL
- history.push({
- pathname: `/config/${splitKey[0]}/${splitKey[1]}`,
- })
- } else {
- history.push({
- pathname: `/config/${splitKey[0]}/!manage`,
- })
- }
- setChosenPanel('configurator')
- }
-
-
- const TreeMenuDropdownMenu = (
-
- {resources ? resources.indexOf(resource) == -1 && resources.indexOf("authz:superuser") == -1 ?
-
-
- {t("ASABConfig|Export")}
-
- :
-
-
-
- {t("ASABConfig|Export")}
-
-
- :
-
-
- {t("ASABConfig|Export")}
-
- }
- setChosenPanel("import")}
- >
-
- {t("ASABConfig|Import")}
-
-
- );
-
- return (
-
- )
-}
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/actions/actions.js b/src/modules/maintenance/ConfigModule/ConfigContainers/actions/actions.js
deleted file mode 100644
index eb1bb5fa7..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/actions/actions.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export const types = {
- CONFIG_CREATED: "asab/ASAB-CONFIG/CONFIG_CREATED",
- CONFIG_REMOVED: "asab/ASAB-CONFIG/CONFIG_REMOVED",
- CONFIG_IMPORTED: "asab/ASAB-CONFIG/CONFIG_IMPORTED"
-}
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/configuration.scss b/src/modules/maintenance/ConfigModule/ConfigContainers/configuration.scss
deleted file mode 100644
index 6972e6e1a..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/configuration.scss
+++ /dev/null
@@ -1,76 +0,0 @@
-.config-container {
- height: 100%;
-
- .card-editor-layout {
- height: 100%;
- width: 100%;
-
- form {
- height: 100%;
- display: flex;
- flex-direction: column;
- }
- }
-
- .card-editor-body {
- height: 100% !important;
- overflow: auto !important;
- overflow-x: hidden !important;
- }
-
- /* ConfigList DataTable */
- .config-list-dt {
- height: 100% !important;
- .row {
- height: 100%;
- .card, .col {
- height: 100% !important;
- }
- .card-body {
- overflow: auto !important;
- }
- .card-footer {
- display: none !important;
- }
- }
- }
-
- /*Config Editor*/
- .pattern-section-dropdown {
- max-height: 400px;
- overflow-y: auto;
- }
-
- .hidden-file-input {
- display: none;
- }
-
- .file-input {
- overflow: hidden;
- display: flex;
- flex-wrap: wrap;
- flex: 1 5;
- input {
- cursor: pointer;
- &:focus {
- box-shadow: none;
- border-color: #ced4da;
- }
- }
- .input-group-text {
- cursor: pointer;
- border-radius: 5px 0 0 5px;
- border-right: none;
- }
- }
-
- .dropdown-export-item, .dropdown-export-item:hover {
- text-decoration: none;
- }
-
- .config-editor-cardbody {
- display: grid;
- align-items: center;
- justify-content: center;
- }
-}
\ No newline at end of file
diff --git a/src/modules/maintenance/ConfigModule/ConfigContainers/reducer.js b/src/modules/maintenance/ConfigModule/ConfigContainers/reducer.js
deleted file mode 100644
index da551130c..000000000
--- a/src/modules/maintenance/ConfigModule/ConfigContainers/reducer.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// Actions
-import {types} from './actions/actions';
-
-const initialState = {
- config_created: false,
- config_removed: false,
- config_imported: false
-}
-
-export default function asabConfigReducer(state = initialState, action) {
- switch (action.type) {
-
- case types.CONFIG_CREATED:
- return {
- config_created: action.config_created
- };
- case types.CONFIG_REMOVED:
- return {
- config_removed: action.config_removed
- }
- case types.CONFIG_IMPORTED:
- return {
- config_imported: action.config_imported
- }
- default:
- return state
- }
-}
diff --git a/src/modules/maintenance/ConfigModule/index.js b/src/modules/maintenance/ConfigModule/index.js
deleted file mode 100644
index 930259262..000000000
--- a/src/modules/maintenance/ConfigModule/index.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import { lazy } from 'react';
-import Module from 'asab-webui/abc/Module';
-import { componentLoader } from 'asab-webui';
-const ConfigContainer = lazy(() => componentLoader(() => import("./ConfigContainers/ConfigContainer")));
-
-import asabConfigReducer from './ConfigContainers/reducer';
-
-import "./ConfigContainers/configuration.scss";
-
-export default class ConfigModule extends Module {
- constructor(app, name) {
- super(app, "ASABConfigModule");
- // Using redux to update items in Coniguration right after the change
- app.ReduxService.addReducer("asab_config", asabConfigReducer);
-
- app.Router.addRoute({
- path: "/config/:configType/:configName",
- exact: true,
- name: "Configuration",
- component: ConfigContainer,
- resource: "asab:config:access"
- });
-
- // Check presence of Maintenance item in sidebar
- let items = app.Navigation.getItems()?.items;
- let isMaintenancePresent = false;
- items.forEach(itm => {
- // If Maintenance present, then append Config as a Maintenance subitem
- if (itm?.name == "Maintenance") {
- itm.children.push({
- name: "Configuration",
- url: "/config/$/$",
- icon: "cil-settings",
- resource: "asab:config:access"
- });
- isMaintenancePresent = true;
- }
- })
-
- // If Maintenance not present in sidebar navigation, add a Maintenance item
- if (!isMaintenancePresent) {
- app.Navigation.addItem({
- name: 'Maintenance',
- icon: "cil-apps-settings",
- children: [
- {
- name: "Configuration",
- url: "/config/$/$",
- icon: "cil-settings",
- resource: "asab:config:access"
- }
- ]
- });
- }
- }
-}
diff --git a/src/modules/maintenance/ServicesModule/ServicesContainers/ServicesContainer.js b/src/modules/maintenance/ServicesModule/ServicesContainers/ServicesContainer.js
deleted file mode 100644
index b8a6ca57d..000000000
--- a/src/modules/maintenance/ServicesModule/ServicesContainers/ServicesContainer.js
+++ /dev/null
@@ -1,549 +0,0 @@
-import React, { useState, useEffect, useRef, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-import ReactJson from 'react-json-view';
-import { useSelector } from 'react-redux';
-
-import { Container, Card, CardBody, CardHeader, Table,
- InputGroup, InputGroupText, Input, InputGroupAddon,
- ButtonGroup
-} from 'reactstrap';
-
-import { CellContentLoader } from 'asab-webui';
-
-import ActionButton from "./components/ActionButton";
-
-export default function ServicesContainer(props) {
-
- const [fullFrameData, setFullFrameData] = useState({});
- const [wsData, setWSData] = useState({});
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(false);
- const [errorMsg, setErrorMsg] = useState("");
-
- const [filter, setFilter] = useState("");
-
- const theme = useSelector(state => state.theme);
-
- const { t } = useTranslation();
-
- // Set up websocket connection
- let wsSubPath = '/ws';
- const serviceName = 'lmio_remote_control';
- let WSUrl = props.app.getWebSocketURL(serviceName, wsSubPath);
- let WSClient = null;
-
- const isMounted = useRef(null);
-
- // Connect to ws on page initialization, close ws connection on page leave
- useEffect(() => {
- isMounted.current = true;
-
- if (WSUrl != undefined) {
- reconnect();
- }
-
- return () => {
- if (WSClient != null) {
- try {
- WSClient.close();
- } catch (e) {
- console.log("Ignored exception: ", e)
- }
- }
-
- isMounted.current = false;
- }
- }, []);
-
-
- // Use memo for data rendering (due to expensive caluclations)
- const data = useMemo(() => {
- let webSocketData = wsData.data;
- // Render ws data
- if(webSocketData && Object.keys(webSocketData)) {
- // Check for delta frame
- if (wsData?.frame_type === "df") {
- // If key in full frame, update data, otherwise append wsData to fullframe object without mutating the original object
- let renderAll = {...fullFrameData, ...{}};
- Object.keys(webSocketData).map((dfd, idx) => {
- if (fullFrameData[dfd]) {
- // New additions / updates to values of object
- const additions = webSocketData[dfd] ? webSocketData[dfd] : {};
- // Append new values / updates to values of object
- let updateValues = {...renderAll[dfd], ...additions}
- // Create a new key-value pair from deltaFrame key and new/updated values
- let newObj = {};
- newObj[dfd] = updateValues;
- // Append new object to fullFrame data object without mutation of original one
- renderAll = {...renderAll, ...newObj};
- } else {
- let newObj = {};
- newObj[dfd] = webSocketData[dfd];
- // Append wsData object to fullFrame object and return it
- renderAll = {...renderAll, ...newObj};
- }
- })
- // Set fullFrame with update data from delta frame
- setFullFrameData(renderAll);
- return renderAll;
- } else {
- // Set full frame with full frame data
- setFullFrameData(webSocketData);
- return webSocketData;
- }
- }
- // Fallback if wsData will not meet the condition requirements
- return fullFrameData;
- }, [wsData]) // If websocket data change, then trigger computation of data rendering
-
-
- // Filter state among data
- const filteredData = useMemo (() => {
- if ((filter != undefined) && (filter.length > 0)) {
- const fltr = filter.toLowerCase();
- let filteredObj = {};
- Object.keys(fullFrameData).map((key, idx) => {
- if (fullFrameData[key].state) {
- if (fullFrameData[key].state.indexOf(fltr) != -1) {
- filteredObj[key] = fullFrameData[key];
- }
- }
- })
- return filteredObj;
- }
- return undefined;
- }, [filter, fullFrameData])
-
- // Reconnect ws method
- const reconnect = () => {
- if (WSClient != null) {
- try {
- WSClient.close();
- } catch (e) {
- console.log("Ignored exception: ", e)
- }
- }
-
- if (isMounted.current === false) return;
-
- WSClient = props.app.createWebSocket(serviceName, wsSubPath);
-
- // TODO: remove onopen
- WSClient.onopen = () => {
- console.log('ws connection open');
- }
-
- WSClient.onmessage = (message) => {
- setLoading(false);
- if (IsJsonString(message.data) == true) {
- let retrievedData = JSON.parse(message.data);
- if (retrievedData && Object.keys(retrievedData)) {
- // Set websocket data
- setWSData(retrievedData);
- }
- setError(false);
- } else {
- setErrorMsg(t("ServicesContainer|Can't display data due to parsing error"));
- setError(true);
- }
- };
-
- WSClient.onerror = (error) => {
- setLoading(false);
- setErrorMsg(t("ServicesContainer|Can't establish websocket connection, data can't be loaded"));
- setError(true);
- setTimeout(() => {
- reconnect();
- }, 3000, this);
- };
- }
-
-
- return (
-
-
-
-
- {t("ServicesContainer|Services")}
-
-
-
-
- {(loading == true) ?
-
- :
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
- {t("ServicesContainer|Service")}
- |
-
- {t("ServicesContainer|Node ID")}
- |
-
- {t("ServicesContainer|Name")}
- |
-
- {t("ServicesContainer|Version")}
- |
-
- |
-
-
-
- {(error == true) ?
-
- {errorMsg} |
-
- :
-
- }
-
-
- }
-
-
-
- )
-}
-
-// Method to render table row with data
-const DataRow = ({data, props}) => {
- const { t } = useTranslation();
-
- // Generate status
- /*
- Cannot use generateStatus func from separate container, cause it causes
- "Rendered more hooks than during the previous render." error
- */
- const generateStatus = (status) => {
- if (status == undefined) {
- return ();
- }
- if (typeof status === "string") {
- return statusTranslations(status);
- }
- if (typeof status === "object") {
- return statusTranslations(status.name);
- }
- return status;
- }
-
- // Translate well known statuses
- const statusTranslations = (status) => {
-
- if (status.toLowerCase() === "running") {
- return ();
- };
- if (status.toLowerCase() === "starting") {
- return ();
- };
- if (status.toLowerCase() === "stopped") {
- return ();
- };
- if (status.toLowerCase() === "unknown") {
- return ();
- };
- return ();
- }
-
- return(
- data && Object.keys(data).map((objKey) => (
-
- ))
- )
-}
-
-// Content of the row in table
-const RowContent = ({props, objKey, data, generateStatus}) => {
- const { t } = useTranslation();
- const theme = useSelector(state => state.theme);
- const [collapseData, setCollapseData] = useState(true);
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- useEffect(() => {
- if (data[objKey]?.state && ((data[objKey]?.state == "stopped") || (data[objKey]?.state == "starting"))) {
- setCollapseData(false);
- } else {
- setCollapseData(true);
- }
- },[data[objKey]?.state])
-
- // Action to start, stop, restart and up the container
- const setAction = async(action, id) => {
- let body = {};
- body["command"] = action;
- const LMIORemoteControlAPI = props.app.axiosCreate('lmio_remote_control');
- try {
- let response = await LMIORemoteControlAPI.post(`/instance/${id}`, body);
- if (response.data.result != "Accepted") {
- throw new Error(`Something went wrong, failed to ${action} container`);
- }
- props.app.addAlert("success", t("ServicesContainer|Service action accepted successfully"));
- } catch(e) {
- console.error(e);
- props.app.addAlert("warning", `${t("ServicesContainer|Service action has been rejected")}. ${e?.response?.data?.message}`, 30);
- }
- setIsSubmitting(false);
- }
-
- return(
- <>
-
-
-
- {collapseData ?
- {setCollapseData(false)}}>
- :
- {setCollapseData(true)}}>
- }
- {generateStatus(data[objKey]?.state ? data[objKey].state : undefined)}
-
- |
-
- {data[objKey]?.service}
- |
-
- {data[objKey]?.node_id?.toString()}
- |
-
- {data[objKey]?.name?.toString()}
- |
-
- {data[objKey]?.advertised_data?.version ? data[objKey]?.advertised_data?.version : data[objKey]?.version ? data[objKey]?.version : "N/A"}
- |
-
-
-
- {setAction("start", data[objKey]?.instance_id), setIsSubmitting(true)}}
- disabled={isSubmitting == true}
- />
- {setAction("stop", data[objKey]?.instance_id), setIsSubmitting(true)}}
- icon="cil-media-stop"
- disabled={isSubmitting == true}
- />
- {setAction("restart", data[objKey]?.instance_id), setIsSubmitting(true)}}
- icon="cil-reload"
- disabled={isSubmitting == true}
- />
- {setAction("up", data[objKey]?.instance_id), setIsSubmitting(true)}}
- icon="cil-media-eject"
- disabled={isSubmitting == true}
- />
-
-
- |
-
- {!collapseData &&
-
-
- {data[objKey]?.type ?
-
- {t("ServicesContainer|Type")}: {data[objKey]?.type?.toString()}
-
- :
- null
- }
- {data[objKey]?.returncode?.toString() ?
-
- {t("ServicesContainer|Return code")}: {data[objKey]?.returncode?.toString()}
-
- :
- null
- }
- {data[objKey]?.error ?
-
- {t("ServicesContainer|Error")}: {data[objKey]?.error?.toString()}
-
- :
- null
- }
- {data[objKey]?.exception ?
-
-
- {t("ServicesContainer|Exception")}:
-
-
-
- :
- null
- }
- {data[objKey]?.console ?
-
-
- {t("ServicesContainer|Console")}:
-
-
-
- :
- null
- }
- {data[objKey]?.detail ?
-
- :
- null
- }
- {data[objKey]?.advertised_data ?
-
- :
- null
- }
- |
-
- }
- >
- )
-}
-
-// Method to display collapsed table
-const CollapsedTable = ({obj, title}) => {
- const theme = useSelector(state => state.theme);
-
- return(
-
-
-
- {title} |
-
-
-
- {Object.keys(obj).length != 0 && Object.entries(obj).map((itms, idx) => {
- return(
-
-
-
- {itms[0] && itms[0].toString()}
-
- |
-
- {itms[1] ?
- (typeof itms[1] == "object") && (Object.keys(itms[1]).length > 0) ?
-
- :
- {itms[1] && itms[1].toString()}
- : "-"}
- |
-
- )
- })}
-
-
- )
-}
-
-
-// Search method
-const Search = ({ search, filterValue, setFilterValue }) => {
-
- return (
-
-
- {search.icon &&
-
-
-
- }
- setFilterValue(e.target.value)}
- placeholder={search.placeholder}
- type="text"
- bsSize="sm"
- />
-
-
- );
-}
-
-// Check if string is valid JSON
-function IsJsonString(str) {
- try {
- JSON.parse(str);
- } catch (e) {
- return false;
- }
- return true;
-}
diff --git a/src/modules/maintenance/ServicesModule/ServicesContainers/components/ActionButton.js b/src/modules/maintenance/ServicesModule/ServicesContainers/components/ActionButton.js
deleted file mode 100644
index cb41d5665..000000000
--- a/src/modules/maintenance/ServicesModule/ServicesContainers/components/ActionButton.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, { useState } from 'react';
-import { Button, Tooltip } from 'reactstrap';
-import { useSelector } from 'react-redux';
-import { ButtonWithAuthz } from 'asab-webui';
-
-const ActionButton = ({
- label,
- onClick,
- icon,
- id,
- disabled=false,
- className="",
- color="",
- outline=false
-}) => {
- const [tooltipOpen, setTooltipOpen] = useState(false);
- const resource = "asab:service:manage";
- const resources = useSelector(state => state.auth?.resources);
-
- const toggle = () => setTooltipOpen(!tooltipOpen);
-
- const title = () => `${label.split(' ')[0]}`;
-
- return (
-
-
-
-
-
- {title()}
-
-
- )
-}
-
-export default ActionButton;
diff --git a/src/modules/maintenance/ServicesModule/ServicesContainers/services.scss b/src/modules/maintenance/ServicesModule/ServicesContainers/services.scss
deleted file mode 100644
index d7e447a9c..000000000
--- a/src/modules/maintenance/ServicesModule/ServicesContainers/services.scss
+++ /dev/null
@@ -1,100 +0,0 @@
-@import "~asab-webui/styles/constants/index.scss";
-
-/* container styles */
-
-.svcs-container {
- height: 100%;
-
-}
-
-/* card body styles */
-
-.services-body {
- overflow: auto;
- padding-top: 0 !important;
- padding-bottom: 0 !important;
-}
-
-/* status indicator styles */
-
-.service-status-circle {
- margin-top: 0.25rem;
- width: 0.8rem;
- height: 0.8rem;
- background: $secondary;
- border-radius: 50%
-}
-
-.service-status-running {
- background: $light-green;
-}
-
-.service-status-starting {
- background: $warning;
-}
-
-.service-status-stopped {
- background: $danger;
-}
-
-/* table styles */
-
-.td-style {
- text-align: center;
- background: $bg-color;
-}
-
-/* collapsed table styles */
-
-.collapsed-data {
- background-color: $bg-color;
-}
-
-.collapsed-data:hover {
- background-color: $bg-color;
-}
-
-.caret-status-div {
- display: inline-flex !important;
- .caret-icon {
- padding-right: 0.75rem;
- padding-top: 0.15rem;
- color: $primary;
- cursor: pointer;
- }
-}
-
-.collapsed-table-row {
- padding-top: 0px !important;
- padding-bottom: 0px !important;
- td {
- padding-top: 0.25em !important;
- padding-bottom: 0.25em !important;
- height: fit-content !important;
- }
- th {
- padding-top: 0.25em !important;
- padding-bottom: 0.25em !important;
- padding-left: 0em !important;
- height: fit-content !important;
- }
-}
-
-.collapsed-heading {
- color: var(--text-secondary-color) !important;
- text-align: inherit !important;
- font-weight: bold !important;
-}
-
-.collapsed-code-value {
- font-size: 100%;
- color: var(--text-color);
-}
-
-.collapsed-console {
- display: inline-flex;
-}
-
-.collapsed-span {
- padding-right: 0.5em;
-}
diff --git a/src/modules/maintenance/ServicesModule/index.js b/src/modules/maintenance/ServicesModule/index.js
deleted file mode 100644
index 79c5ffd29..000000000
--- a/src/modules/maintenance/ServicesModule/index.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import { lazy } from 'react';
-import Module from 'asab-webui/abc/Module';
-import { componentLoader } from 'asab-webui';
-const ServicesContainer = lazy(() => componentLoader(() => import("./ServicesContainers/ServicesContainer")));
-
-import "./ServicesContainers/services.scss";
-
-export default class ServicesModule extends Module {
- constructor(app, name) {
- super(app, "ASABServicesModule");
-
- app.Router.addRoute({
- path: "/services",
- exact: true,
- name: "Services",
- component: ServicesContainer,
- resource: "asab:service:access"
- });
-
- // Check presence of Maintenance item in sidebar
- let items = app.Navigation.getItems()?.items;
- let isMaintenancePresent = false;
- items.forEach(itm => {
- // If Maintenance present, then append Microservices as a Maintenance subitem
- if (itm?.name == "Maintenance") {
- itm.children.push({
- name: "Services",
- url: "/services",
- icon: "cil-list",
- resource: "asab:service:access"
- });
- isMaintenancePresent = true;
- }
- })
-
- // If Maintenance not present in sidebar navigation, add a Maintenance item
- if (!isMaintenancePresent) {
- app.Navigation.addItem({
- name: 'Maintenance',
- icon: "cil-apps-settings",
- children: [
- {
- name: "Services",
- url: "/services",
- icon: "cil-list",
- resource: "asab:service:access"
- }
- ]
- });
- }
- }
-}