diff --git a/src/common/data-managers/rest-data-manager.js b/src/common/data-managers/rest-data-manager.js index 271f4c6..fb23eff 100644 --- a/src/common/data-managers/rest-data-manager.js +++ b/src/common/data-managers/rest-data-manager.js @@ -99,7 +99,7 @@ class RestDataManager { }); if (this.onLoad != null) { - this.onLoad(data); + this.onLoad(data, token); } }).catch(error => { AppDispatcher.dispatch({ @@ -121,7 +121,7 @@ class RestDataManager { }); if (this.onLoad != null) { - this.onLoad(data); + this.onLoad(data, token); } }).catch(error => { AppDispatcher.dispatch({ @@ -133,16 +133,50 @@ class RestDataManager { } - add(object, token = null, param = null) { + add(object, token = null, param = null, subObjects = null) { var obj = {}; obj[this.type] = this.filterKeys(object); - RestAPI.post(this.requestURL('load/add',null,param), obj, token).then(response => { AppDispatcher.dispatch({ type: this.type + 's/added', data: response[this.type], token: token }); + + // check if POST is done for import of object and issue dispatches of sub-objects + if (subObjects !== null){ + // there are sub-objects to be added for an import + for (let objectType of subObjects){ + let type = Object.keys(objectType) // type can be dashboards, configs, widgets, ... + type = type[0]; + for (let newObj of objectType[type]){ + + // set the ID of the object that the sub-object shall be associated with + if(type === "configs" || type === "dashboards"){ + // the main object is a scenario + newObj.scenarioID = response[this.type].id + } else if (type === "widgets") { + // the main object is a dashboard + newObj.dashboardID = response[this.type].id + } else if (type === "signals") { + // the main object is a component configuration + newObj.configID = response[this.type].id + } + + // iterate over all objects of type 'type' add issue add dispatch + AppDispatcher.dispatch({ + type: type + '/start-add', + data: newObj, + token: token + }) + + } + } + + + } + + }).catch(error => { AppDispatcher.dispatch({ type: this.type + 's/add-error', diff --git a/src/componentconfig/config-store.js b/src/componentconfig/config-store.js index b67377d..6a1911e 100644 --- a/src/componentconfig/config-store.js +++ b/src/componentconfig/config-store.js @@ -29,10 +29,38 @@ class ConfigStore extends ArrayStore { case 'configs/loaded': - ConfigsDataManager.loadSignals(action.token, action.data); ConfigsDataManager.loadFiles(action.token, action.data); return super.reduce(state, action); + case 'configs/start-add': + // Check if this is a recursive component config import or not + if (action.data.hasOwnProperty("outputMapping") || action.data.hasOwnProperty("inputMapping")) { + // import + let subObjects = [] + let outputMapping = {} + let inputMapping = {} + + if (action.data.hasOwnProperty("outputMapping")){ + outputMapping["signals"] = action.data.outputMapping + subObjects.push(outputMapping) + delete action.data.outputMapping; // remove outputMapping signals from config object + } + if (action.data.hasOwnProperty("inputMapping")){ + inputMapping["signals"] = action.data.inputMapping + subObjects.push(inputMapping) + delete action.data.inputMapping; // remove inputMapping signals from config object + } + + // action.data should now contain the config and no sub-objects + // sub-objects are treated in add method of RestDataManager + this.dataManager.add(action.data, action.token,action.param, subObjects); + return state + + } else { + // no import + return super.reduce(state, action); + } + default: return super.reduce(state, action); diff --git a/src/componentconfig/configs-data-manager.js b/src/componentconfig/configs-data-manager.js index c0ac6c9..64245a8 100644 --- a/src/componentconfig/configs-data-manager.js +++ b/src/componentconfig/configs-data-manager.js @@ -26,43 +26,35 @@ class ConfigDataManager extends RestDataManager { this.onLoad = this.onConfigsLoad; } - onConfigsLoad(data) { + onConfigsLoad(data, token) { if (!Array.isArray(data)) data = [ data ]; - for (let config of data) - this.loadICData(config); - } + for (let config of data) { - loadICData(config) { - AppDispatcher.dispatch({ - type: 'icData/prepare', - inputLength: parseInt(config.inputLength, 10), - outputLength: parseInt(config.outputLength, 10), - id: config.icID - }); - } + // prepare IC data + AppDispatcher.dispatch({ + type: 'icData/prepare', + inputLength: parseInt(config.inputLength, 10), + outputLength: parseInt(config.outputLength, 10), + id: config.icID + }); - loadSignals(token, configs){ - - for (let config of configs) { // request in signals - RestAPI.get(this.makeURL('/signals?direction=in&configID=' + config.id), token).then(response => { - AppDispatcher.dispatch({ - type: 'signals/loaded', - data: response.signals - }); + AppDispatcher.dispatch({ + type: 'signals/start-load', + token: token, + param: '?direction=in&configID=' + config.id, }); // request out signals - RestAPI.get(this.makeURL('/signals?direction=out&configID=' + config.id), token).then(response => { - AppDispatcher.dispatch({ - type: 'signals/loaded', - data: response.signals - }); + AppDispatcher.dispatch({ + type: 'signals/start-load', + token: token, + param: '?direction=out&configID=' + config.id, }); - } + } loadFiles(token, configs){ diff --git a/src/componentconfig/import-config.js b/src/componentconfig/import-config.js index 89c1986..b11a69e 100644 --- a/src/componentconfig/import-config.js +++ b/src/componentconfig/import-config.js @@ -17,18 +17,19 @@ import React from 'react'; import { FormGroup, FormControl, FormLabel } from 'react-bootstrap'; -import _ from 'lodash'; import Dialog from '../common/dialogs/dialog'; class ImportConfigDialog extends React.Component { imported = false; + valid = false; constructor(props) { super(props); this.state = { - config: {} + config: {}, + name: '', }; } @@ -39,12 +40,13 @@ class ImportConfigDialog extends React.Component { return; } - this.props.onClose(this.state.config); + this.props.onClose(this.state); } resetState = () => { this.setState({ - config: {} + config: {}, + name: '' }); this.imported = false; @@ -58,46 +60,65 @@ class ImportConfigDialog extends React.Component { } // create file reader - const reader = new FileReader(); - const self = this; + let reader = new FileReader(); + let self = this; reader.onload = event => { const config = JSON.parse(event.target.result); - config.icID = this.props.ics.length > 0 ? this.props.ics[0]._id : null; - self.imported = true; - - this.setState({ config: config }); + self.valid = true; + this.setState({name: config.name, config: config }); }; reader.readAsText(file); } - handleICChange = event => { - const config = this.state.config; + handleChange(e, index) { + this.setState({ [e.target.id]: e.target.value }); + } - config.icID = event.target.value; + validateForm(target) { + // check all controls + let name = true; - this.setState({ config: config }); + if (this.state.name === '') { + name = false; + } + this.valid = name; + + // return state to control + if (target === 'name'){ + return name; + } } render() { return ( - this.onClose(c)} onReset={this.resetState} valid={this.imported}> + this.onClose(c)} + onReset={() => this.resetState()} + valid={this.valid} >
Component Configuration File - - Infrastructure Component - - {this.props.ics.map(ic => ( - - ))} - + + Name + this.handleChange(e)} + /> +
diff --git a/src/dashboard/dashboard-store.js b/src/dashboard/dashboard-store.js index 4e850ab..5219135 100644 --- a/src/dashboard/dashboard-store.js +++ b/src/dashboard/dashboard-store.js @@ -18,4 +18,40 @@ import ArrayStore from '../common/array-store'; import DashboardsDataManager from './dashboards-data-manager'; -export default new ArrayStore('dashboards', DashboardsDataManager); +class DashboardStore extends ArrayStore { + constructor() { + super('dashboards', DashboardsDataManager); + } + + reduce(state, action) { + + switch (action.type) { + case 'dashboards/start-add': + + // Check if this is a recursive dashboard import or not + if (action.data.hasOwnProperty("widgets")) { + // import + let subObjects = [] + let widgets = {} + widgets["widgets"] = action.data.widgets + subObjects.push(widgets) + delete action.data.widgets; // remove widgets from dashboard object + + // action.data should now contain the dashboard and no sub-objects + // sub-objects are treated in add method of RestDataManager + this.dataManager.add(action.data, action.token,action.param, subObjects); + return state + + } else { + // no import + return super.reduce(state, action); + } + + default: + return super.reduce(state, action); + } + } + +} + +export default new DashboardStore(); diff --git a/src/dashboard/dashboards-data-manager.js b/src/dashboard/dashboards-data-manager.js index b35c58d..451d3bf 100644 --- a/src/dashboard/dashboards-data-manager.js +++ b/src/dashboard/dashboards-data-manager.js @@ -16,5 +16,29 @@ ******************************************************************************/ import RestDataManager from '../common/data-managers/rest-data-manager'; +import AppDispatcher from "../common/app-dispatcher"; -export default new RestDataManager('dashboard', '/dashboards'); +class DashboardsDataManager extends RestDataManager{ + constructor() { + super('dashboard', '/dashboards'); + this.onLoad = this.onDashboardsLoad + } + + onDashboardsLoad(data, token){ + + if (!Array.isArray(data)) { + data = [data]; + } + + for (let dashboard of data){ + AppDispatcher.dispatch({ + type: 'widgets/start-load', + token: token, + param: '?dashboardID=' + dashboard.id + }); + } + } + +} + +export default new DashboardsDataManager(); diff --git a/src/dashboard/import-dashboard.js b/src/dashboard/import-dashboard.js index b44e1f8..47e5216 100644 --- a/src/dashboard/import-dashboard.js +++ b/src/dashboard/import-dashboard.js @@ -67,27 +67,6 @@ class ImportDashboardDialog extends React.Component { // read IC const dashboard = JSON.parse(event.target.result); - /*let defaultIC = ""; - if (self.props.configs != null) { - defaultIC = self.props.configs[0].icID; - } - - dashboard.widgets.forEach(widget => { - switch (widget.type) { - case 'Value': - case 'Plot': - case 'Table': - case 'PlotTable': - case 'Gauge': - break; - - default: - break; - } - }); - - */ - self.imported = true; self.valid = true; self.setState({ name: dashboard.name, widgets: dashboard.widgets, grid: dashboard.grid }); @@ -107,21 +86,36 @@ class ImportDashboardDialog extends React.Component { this.valid = name; // return state to control - if (target === 'name') return name ? "success" : "error"; + if (target === 'name'){ + return name; + } } render() { return ( - this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> + this.onClose(c)} + onReset={() => this.resetState()} + valid={this.valid}>
Dashboard File this.loadFile(e.target.files)} /> - + Name - this.handleChange(e)} /> + this.handleChange(e)} + /> diff --git a/src/scenario/import-scenario.js b/src/scenario/import-scenario.js index 1e454eb..a6b9a23 100644 --- a/src/scenario/import-scenario.js +++ b/src/scenario/import-scenario.js @@ -32,6 +32,7 @@ class ImportScenarioDialog extends React.Component { name: '', running: '', configs: [], + dashboards: [], startParameters: {} }; } @@ -51,15 +52,6 @@ class ImportScenarioDialog extends React.Component { } handleChange(e, index) { - /*if (e.target.id === 'icID') { - const configs = this.state.configs; - configs[index].icID = JSON.parse(e.target.value); - - this.setState({ configs: configs }); - - return; - }*/ - this.setState({ [e.target.id]: e.target.value }); // check all controls @@ -94,7 +86,7 @@ class ImportScenarioDialog extends React.Component { self.imported = true; self.valid = true; - self.setState({ name: scenario.name, configs: scenario.configs, startParameters: scenario.startParameters, running: scenario.running }); + self.setState({ name: scenario.name, configs: scenario.configs, dashboards: scenario.dashboards, startParameters: scenario.startParameters, running: scenario.running }); }; reader.readAsText(file); diff --git a/src/scenario/scenario-store.js b/src/scenario/scenario-store.js index 777475c..15a14f3 100644 --- a/src/scenario/scenario-store.js +++ b/src/scenario/scenario-store.js @@ -19,4 +19,50 @@ import ScenariosDataManager from './scenarios-data-manager'; import ArrayStore from '../common/array-store'; -export default new ArrayStore('scenarios', ScenariosDataManager); +class ScenarioStore extends ArrayStore{ + constructor() { + super('scenarios', ScenariosDataManager); + } + + reduce(state, action) { + switch (action.type) { + + case 'scenarios/start-add': + + // Check if this is a recursive scenario import or not + if (action.data.hasOwnProperty("configs") || action.data.hasOwnProperty("dashboards")) { + // import + let subObjects = [] + let configs = {} + let dashboards = {} + + if (action.data.hasOwnProperty("configs")){ + configs["configs"] = action.data.configs + subObjects.push(configs) + delete action.data.configs; // remove configs from scenario object + } + if (action.data.hasOwnProperty("dashboards")){ + dashboards["dashboards"] = action.data.dashboards + subObjects.push(dashboards) + delete action.data.dashboards; // remove dashboards from scenario object + } + + // action.data should now contain the scenario and no sub-objects + // sub-objects are treated in add method of RestDataManager + this.dataManager.add(action.data, action.token,action.param, subObjects); + return state + + } else { + // no import + return super.reduce(state, action); + } + + default: + return super.reduce(state, action); + } + } + + +} + +export default new ScenarioStore(); diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 808307e..2f838ab 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -40,10 +40,12 @@ import DeleteDialog from '../common/dialogs/delete-dialog'; import EditConfigDialog from "../componentconfig/edit-config"; import EditSignalMapping from "../signal/edit-signal-mapping"; import FileStore from "../file/file-store" +import WidgetStore from "../widget/widget-store"; class Scenario extends React.Component { + static getStores() { - return [ ScenarioStore, ConfigStore, DashboardStore, ICStore, LoginStore, SignalStore, FileStore]; + return [ ScenarioStore, ConfigStore, DashboardStore, ICStore, LoginStore, SignalStore, FileStore, WidgetStore]; } static calculateState(prevState, props) { @@ -96,7 +98,6 @@ class Scenario extends React.Component { } componentDidMount() { - //load selected scenario AppDispatcher.dispatch({ type: 'scenarios/start-load', @@ -104,29 +105,14 @@ class Scenario extends React.Component { token: this.state.sessionToken }); - // load component configurations for selected scenario - AppDispatcher.dispatch({ - type: 'configs/start-load', - token: this.state.sessionToken, - param: '?scenarioID='+this.state.scenario.id, - }); - - // load dashboards of selected scenario - AppDispatcher.dispatch({ - type: 'dashboards/start-load', - token: this.state.sessionToken, - param: '?scenarioID='+this.state.scenario.id, - }); - // load ICs to enable that component configs and dashboards work with them AppDispatcher.dispatch({ type: 'ics/start-load', - token: this.state.sessionToken, + token: this.state.sessionToken }); - - } + /* ############################################## * Component Configuration modification methods ############################################## */ @@ -180,33 +166,47 @@ class Scenario extends React.Component { }); } - importConfig(config){ + importConfig(data){ this.setState({ importConfigModal: false }); - if (config == null) { + if (data == null) { return; } - config.scenario = this.state.scenario.id; + let newConfig = JSON.parse(JSON.stringify(data.config)) + + newConfig["scenarioID"] = this.state.scenario.id; + newConfig.name = data.name; AppDispatcher.dispatch({ type: 'configs/start-add', - data: config, + data: newConfig, token: this.state.sessionToken }); - - this.setState({ scenario: {} }, () => { - AppDispatcher.dispatch({ - type: 'scenarios/start-load', - data: this.props.match.params.scenario, - token: this.state.sessionToken - }); - }); } exportConfig(index) { // filter properties - const config = Object.assign({}, this.state.configs[index]); + let config = JSON.parse(JSON.stringify(this.state.configs[index])); + + let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10)))); + signals.forEach((signal) => { + delete signal.configID; + delete signal.id; + }) + + // two separate lists for inputMapping and outputMapping + let inputSignals = signals.filter(s => s.direction === 'in'); + let outputSignals = signals.filter(s => s.direction === 'out'); + + // add signal mappings to config + config["inputMapping"] = inputSignals; + config["outputMapping"] = outputSignals; + + delete config.id; + delete config.scenarioID; + delete config.inputLength; + delete config.outputLength; // show save dialog const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); @@ -311,9 +311,12 @@ class Scenario extends React.Component { this.setState({ importDashboardModal: false }); if (data) { + let newDashboard = JSON.parse(JSON.stringify(data)); + newDashboard["scenarioID"] = this.state.scenario.id; + AppDispatcher.dispatch({ type: 'dashboards/start-add', - data, + data: newDashboard, token: this.state.sessionToken, }); } @@ -321,9 +324,16 @@ class Scenario extends React.Component { exportDashboard(index) { // filter properties - const dashboard = Object.assign({}, this.state.dashboards[index]); + let dashboard = JSON.parse(JSON.stringify(this.state.dashboards[index])); - // TODO get elements recursively + let widgets = JSON.parse(JSON.stringify(WidgetStore.getState().filter(w => w.dashboardID === parseInt(dashboard.id, 10)))); + widgets.forEach((widget) => { + delete widget.dashboardID; + delete widget.id; + }) + dashboard["widgets"] = widgets; + delete dashboard.scenarioID; + delete dashboard.id; // show save dialog const blob = new Blob([JSON.stringify(dashboard, null, 2)], { type: 'application/json' }); diff --git a/src/scenario/scenarios-data-manager.js b/src/scenario/scenarios-data-manager.js index ec4a442..a59d9a4 100644 --- a/src/scenario/scenarios-data-manager.js +++ b/src/scenario/scenarios-data-manager.js @@ -16,41 +16,36 @@ ******************************************************************************/ import RestDataManager from '../common/data-managers/rest-data-manager'; -import RestAPI from "../common/api/rest-api"; import AppDispatcher from "../common/app-dispatcher"; class ScenariosDataManager extends RestDataManager { constructor() { super('scenario', '/scenarios'); + + this.onLoad = this.onScenariosLoad } - getComponentConfigs(token, id) { - RestAPI.get(this.makeURL('/scenarios/' + id + '/configs'), token).then(response => { - AppDispatcher.dispatch({ - type: 'scenarios/configs', - configs: response.configs - }); - }).catch(error => { - AppDispatcher.dispatch({ - type: 'scenarios/configs-error', - error: error - }); - }); - } + onScenariosLoad(data, token){ - getDashboards(token, id) { - RestAPI.get(this.makeURL('/scenarios/' + id + '/dashboards'), token).then(response => { - AppDispatcher.dispatch({ - type: 'scenarios/dashboards', - dashboards: response.dashboards - }); - }).catch(error => { - AppDispatcher.dispatch({ - type: 'scenarios/dashboards-error', - error: error - }); - }); - } + if (!Array.isArray(data)) { + data = [data]; + } + for (let scenario of data){ + AppDispatcher.dispatch({ + type: 'configs/start-load', + token: token, + param: '?scenarioID=' + scenario.id + }); + + AppDispatcher.dispatch({ + type: 'dashboards/start-load', + token: token, + param: '?scenarioID=' + scenario.id + }); + + // TODO add dispatch for files + } + } } export default new ScenariosDataManager(); diff --git a/src/scenario/scenarios.js b/src/scenario/scenarios.js index 8813190..22711a7 100644 --- a/src/scenario/scenarios.js +++ b/src/scenario/scenarios.js @@ -23,6 +23,10 @@ import FileSaver from 'file-saver'; import AppDispatcher from '../common/app-dispatcher'; import ScenarioStore from './scenario-store'; import LoginStore from '../user/login-store'; +import DashboardStore from '../dashboard/dashboard-store'; +import WidgetStore from "../widget/widget-store"; +import ConfigStore from '../componentconfig/config-store'; +import SignalStore from '../signal/signal-store' import Icon from '../common/icon'; import Table from '../common/table'; @@ -33,18 +37,20 @@ import ImportScenarioDialog from './import-scenario'; import DeleteDialog from '../common/dialogs/delete-dialog'; + class Scenarios extends Component { + static getStores() { - return [ ScenarioStore, LoginStore ]; + return [ScenarioStore, LoginStore, DashboardStore, WidgetStore, ConfigStore, SignalStore]; } static calculateState() { - const scenarios = ScenarioStore.getState(); - const sessionToken = LoginStore.getState().token; return { - scenarios, - sessionToken, + scenarios: ScenarioStore.getState(), + dashboards: DashboardStore.getState(), + configs: ConfigStore.getState(), + sessionToken: LoginStore.getState().token, newModal: false, deleteModal: false, @@ -63,23 +69,20 @@ class Scenarios extends Component { }); } - componentDidUpdate() {} - closeNewModal(data) { - this.setState({ newModal : false }); - - if (data) { + if(data) { AppDispatcher.dispatch({ type: 'scenarios/start-add', - data, - token: this.state.sessionToken + data: data, + token: this.state.sessionToken, }); } + this.setState({ newModal: false }); } showDeleteModal(id) { // get scenario by id - var deleteScenario; + let deleteScenario; this.state.scenarios.forEach((scenario) => { if (scenario.id === id) { @@ -97,6 +100,16 @@ class Scenarios extends Component { return; } + this.state.dashboards.forEach((dashboard) => { + if (dashboard.id === this.state.modalScenario.id) { + AppDispatcher.dispatch({ + type: 'dashboards/start-remove', + data: dashboard, + token: this.state.sessionToken + }) + } + }); + AppDispatcher.dispatch({ type: 'scenarios/start-remove', data: this.state.modalScenario, @@ -118,7 +131,7 @@ class Scenarios extends Component { } closeEditModal(data) { - this.setState({ editModal : false }); + this.setState({ editModal: false }); if (data != null) { AppDispatcher.dispatch({ @@ -135,8 +148,8 @@ class Scenarios extends Component { if (data) { AppDispatcher.dispatch({ type: 'scenarios/start-add', - data, - token: this.state.sessionToken + data: data, + token: this.state.sessionToken, }); } } @@ -150,14 +163,54 @@ class Scenarios extends Component { }; exportScenario(index) { - // filter properties - let scenario = Object.assign({}, this.state.scenarios[index]); + // copy by value by converting to JSON and back + // otherwise, IDs of state objects will be deleted + let scenario = JSON.parse(JSON.stringify(this.state.scenarios[index])); + let configs = JSON.parse(JSON.stringify(this.state.configs.filter(config => config.scenarioID === scenario.id))); + let dashboards = JSON.parse(JSON.stringify(this.state.dashboards.filter(dashb => dashb.scenarioID === scenario.id))); + + // create JSON object and add component configs delete scenario.id; + let jsonObj = scenario; - // TODO request missing scenario parameters (Dashboards and component configs) recursively for export + configs.forEach((config) => { + let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10)))); + signals.forEach((signal) => { + delete signal.configID; + delete signal.id; + }) - // show save dialog - const blob = new Blob([JSON.stringify(scenario, null, 2)], { type: 'application/json' }); + // two separate lists for inputMapping and outputMapping + let inputSignals = signals.filter(s => s.direction === 'in'); + let outputSignals = signals.filter(s => s.direction === 'out'); + + // add signal mappings to config + config["inputMapping"] = inputSignals; + config["outputMapping"] = outputSignals; + + delete config.id; + delete config.scenarioID; + delete config.inputLength; + delete config.outputLength; + }) + jsonObj["configs"] = configs; + + // add Dashboards and Widgets to JSON object + dashboards.forEach((dboard) => { + let widgets = JSON.parse(JSON.stringify(WidgetStore.getState().filter(w => w.dashboardID === parseInt(dboard.id, 10)))); + widgets.forEach((widget) => { + delete widget.dashboardID; + delete widget.id; + }) + dboard["widgets"] = widgets; + delete dboard.scenarioID; + delete dboard.id; + }); + jsonObj["dashboards"] = dashboards; + + + // create JSON string and show save dialog + const blob = new Blob([JSON.stringify(jsonObj, null, 2)], { type: 'application/json' }); FileSaver.saveAs(blob, 'scenario - ' + scenario.name + '.json'); } diff --git a/src/signal/signals-data-manager.js b/src/signal/signals-data-manager.js index 768bc7a..2e6cf98 100644 --- a/src/signal/signals-data-manager.js +++ b/src/signal/signals-data-manager.js @@ -27,7 +27,6 @@ class SignalsDataManager extends RestDataManager{ reloadConfig(token, data){ // request in signals - console.log("Reloading component config due to signal add/remove") RestAPI.get(this.makeURL('/configs/' + data.configID), token).then(response => { AppDispatcher.dispatch({ type: 'configs/edited',