Unverified Commit 1efc1127 authored by Kevin Di Lallo's avatar Kevin Di Lallo Committed by GitHub
Browse files

Merge pull request #58 from pastorsx/sp_dev_sp36_frontend1

frontend update (clone feature, vis fixes, editable attributes)
parents 3d89b1b2 104d05f0
Loading
Loading
Loading
Loading
+92 −24
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import { Checkbox } from '@rmwc/checkbox';
import { Typography } from '@rmwc/typography';

import { updateObject } from '../../util/object-util';
import { createUniqueName } from '../../util/elem-utils';

import IDSelect from '../../components/helper-components/id-select';
import CancelApplyPair from '../../components/helper-components/cancel-apply-pair';
import NCGroup from '../../components/helper-components/nc-group';
@@ -56,7 +58,13 @@ import {
  setElemFieldErr
} from '../../util/elem-utils';

import { CFG_ELEM_MODE_EDIT, cfgElemUpdate } from '../../state/cfg';
import {
  CFG_ELEM_MODE_NEW,
  CFG_ELEM_MODE_EDIT,
  CFG_ELEM_MODE_CLONE,
  cfgElemUpdate,
  cfgElemClone
} from '../../state/cfg';

import {
  TYPE_CFG,
@@ -111,6 +119,7 @@ import {
  CFG_ELEM_EGRESS_SVC_MAP,
  CFG_BTN_NEW_ELEM,
  CFG_BTN_DEL_ELEM,
  CFG_BTN_CLONE_ELEM,

  // Layout type
  MEEP_COMPONENT_TABLE_LAYOUT
@@ -915,20 +924,22 @@ const getParentTypes = type => {
};

const buttonStyles = {
  marginRight: 5
  marginRight: 5,
  width: 100
};

const ElementCfgButtons = ({
  configuredElement,
  configMode,
  onNewElement,
  onDeleteElement
  onDeleteElement,
  onCloneElement
}) => {
  const canCreateNewElement = () => {
    return !configuredElement;
  };

  const canDeleteElement = () => {
  const canDeleteOrCloneElement = () => {
    return configuredElement && configMode === CFG_ELEM_MODE_EDIT;
  };

@@ -949,15 +960,47 @@ const ElementCfgButtons = ({
        data-cy={CFG_BTN_DEL_ELEM}
        style={buttonStyles}
        onClick={() => onDeleteElement()}
        disabled={!canDeleteElement()}
        disabled={!canDeleteOrCloneElement()}
      >
        DELETE
      </Button>

      <Button
        outlined
        data-cy={CFG_BTN_CLONE_ELEM}
        style={buttonStyles}
        onClick={() => onCloneElement()}
        disabled={!canDeleteOrCloneElement()}
      >
        CLONE
      </Button>
    </>
  );
};

const HeaderGroup = ({ element, onTypeChange, onUpdate, disabled }) => {
const getSuggestedName = ( type, elements ) => {
  var suggestedPrefix = '';
  switch(type) {
  case ELEMENT_TYPE_UE_APP:
    suggestedPrefix = 'ue-app';
    break;
  case ELEMENT_TYPE_EDGE_APP:
    suggestedPrefix = 'edge-app';
    break;
  case ELEMENT_TYPE_CLOUD_APP:
    suggestedPrefix = 'cloud-app';
    break;
  case ELEMENT_TYPE_DC:
    suggestedPrefix = 'cloud';
    break;
  default:
    suggestedPrefix = type.toLowerCase();
  }

  return createUniqueName(elements, suggestedPrefix);
};

const HeaderGroup = ({ element, onTypeChange, onUpdate, typeDisabled, parentDisabled, nameDisabled }) => {
  var type = getElemFieldVal(element, FIELD_TYPE) || '';
  var parent = getElemFieldVal(element, FIELD_PARENT) || '';
  var parentElements = element.parentElements || [parent];
@@ -965,23 +1008,25 @@ const HeaderGroup = ({ element, onTypeChange, onUpdate, disabled }) => {
  return (
    <>
      <Grid style={{ marginTop: 10 }}>
        {type !== 'SCENARIO' && (
          <IDSelect
            label="Element Type"
            span={6}
            options={elementTypes}
            onChange={elem => onTypeChange(elem.target.value)}
            value={type}
          disabled={disabled}
            disabled={typeDisabled}
            cydata={CFG_ELEM_TYPE}
          />
        {type && (
        )}
        {type && type !== 'SCENARIO' && (
          <IDSelect
            label="Parent Node"
            span={6}
            options={parentElements}
            onChange={elem => onUpdate(FIELD_PARENT, elem.target.value, null)}
            value={parent}
            disabled={disabled}
            disabled={parentDisabled}
            cydata={CFG_ELEM_PARENT}
          />
        )}
@@ -994,7 +1039,7 @@ const HeaderGroup = ({ element, onTypeChange, onUpdate, disabled }) => {
          validate={validateName}
          label="Unique Element Name"
          fieldName={FIELD_NAME}
          disabled={disabled}
          disabled={nameDisabled}
          cydata={CFG_ELEM_NAME}
        />
      </Grid>
@@ -1016,6 +1061,17 @@ export class CfgNetworkElementContainer extends Component {
    this.props.cfgElemUpdate(updatedElem);
  }

  // Element clone handler
  onCloneElement(newName) {
    var clonedElem = updateObject({}, this.props.configuredElement);
    setElemFieldVal(clonedElem, FIELD_NAME, newName);
    setElemFieldVal(clonedElem, FIELD_PARENT, null);
    var elementType = getElemFieldVal(clonedElem, FIELD_TYPE);
    clonedElem.parentElements = this.elementsOfType(getParentTypes(elementType));

    this.props.cfgElemClone(clonedElem);
  }

  // Retrieve names of elements with matching type
  elementsOfType(types) {
    return _.chain(this.props.tableData)
@@ -1038,6 +1094,9 @@ export class CfgNetworkElementContainer extends Component {

    elem.parentElements = this.elementsOfType(getParentTypes(elementType));

    if (this.props.configMode !== CFG_ELEM_MODE_CLONE) {
      setElemFieldVal(elem, FIELD_NAME, getSuggestedName(elementType, this.props.tableData));
    }
    this.props.cfgElemUpdate(elem);
  }

@@ -1046,13 +1105,13 @@ export class CfgNetworkElementContainer extends Component {
    return (
      <div className="cfg-network-element-div" style={styles.outer}>
        <Grid>
          <GridCell span={7}>
          <GridCell span={12}>
            <div style={styles.block}>
              <Typography use="headline6">Element Configuration</Typography>
            </div>
          </GridCell>
          <GridCell span={5}>
            <GridInner align={'right'}>
          <GridCell span={12}>
            <GridInner align={'left'}>
              <GridCell span={12}>
                <ElementCfgButtons
                  configuredElement={element}
@@ -1061,6 +1120,9 @@ export class CfgNetworkElementContainer extends Component {
                  onDeleteElement={() => {
                    this.props.onDeleteElement(element);
                  }}
                  onCloneElement={() => {
                    this.onCloneElement(createUniqueName(this.props.tableData, getElemFieldVal(element, FIELD_NAME) + '-copy'));
                  }}
                />
              </GridCell>
            </GridInner>
@@ -1077,7 +1139,9 @@ export class CfgNetworkElementContainer extends Component {
              onUpdate={(name, val, err) => {
                this.onUpdateElement(name, val, err);
              }}
              disabled={this.props.configMode === CFG_ELEM_MODE_EDIT}
              typeDisabled={this.props.configMode === CFG_ELEM_MODE_CLONE || this.props.configMode === CFG_ELEM_MODE_EDIT}
              parentDisabled={this.props.configMode === CFG_ELEM_MODE_EDIT}
              nameDisabled={getElemFieldVal(element, FIELD_TYPE) === ELEMENT_TYPE_SCENARIO && this.props.configMode !== CFG_ELEM_MODE_NEW}
            />

            <TypeRelatedFormFields
@@ -1095,10 +1159,12 @@ export class CfgNetworkElementContainer extends Component {
            </div>

            <CancelApplyPair
              saveDisabled={(this.props.isModified === false) ? true : false}
              onCancel={this.props.onCancelElement}
              onApply={() => {
                this.props.onSaveElement(element);
                (this.props.configMode === CFG_ELEM_MODE_CLONE) ? this.props.onApplyCloneElement(element) : this.props.onSaveElement(element);
              }}

            />
          </>
        )}
@@ -1131,13 +1197,15 @@ const mapStateToProps = state => {
    tableData: state.cfg.table.entries,
    configuredElement: state.cfg.elementConfiguration.configuredElement,
    configMode: state.cfg.elementConfiguration.configurationMode,
    isModified: state.cfg.elementConfiguration.isModified,
    errorMessage: state.cfg.elementConfiguration.errorMessage
  };
};

const mapDispatchToProps = dispatch => {
  return {
    cfgElemUpdate: element => dispatch(cfgElemUpdate(element))
    cfgElemUpdate: element => dispatch(cfgElemUpdate(element)),
    cfgElemClone: element => dispatch(cfgElemClone(element))
  };
};

+38 −6
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import IDExportScenarioDialog from '../../components/dialogs/id-export-scenario-

import {
  cfgElemNew,
  cfgElemClone,
  cfgElemEdit,
  cfgElemClear,
  cfgElemSetErrMsg,
@@ -132,7 +133,7 @@ class CfgPageContainer extends Component {
      this.props.cfg.elementConfiguration.configurationMode ===
      CFG_ELEM_MODE_NEW
    ) {
      this.props.newScenarioElem(element);
      this.props.newScenarioElem(element, true);
    } else {
      this.props.updateScenarioElem(element);
    }
@@ -141,6 +142,26 @@ class CfgPageContainer extends Component {
    this.props.cfgElemClear();
  }

  // CLONE
  onCloneElement() {
    this.props.cfgElemClone();
  }

  // CLONE
  onApplyCloneElement(element) {
    // Validate network element
    if (this.validateNetworkElement(element) === false) {
      return;
    }

    this.props.cloneScenarioElem(element);

    //force update on the visual aspect of the scenario
    //this.props.updateScenario();

    this.props.cfgElemClear();
  }

  // DELETE
  onDeleteElement(element) {
    this.props.deleteScenarioElem(element);
@@ -161,9 +182,19 @@ class CfgPageContainer extends Component {
    return -1;
  }

  findOtherThanSelfIndexByKeyValue(_array, key, value, exceptionId) {
    for (var i = 0; i < _array.length; i++) {
      if (getElemFieldVal(_array[i], key) === value) {
        if (_array[i].id !== exceptionId) {
          return i;
        }
      }
    }
    return -1;
  }

  // Validate new network element form field entries
  validateNetworkElement(element) {
    var configMode = this.props.cfg.elementConfiguration.configurationMode;
    var data = this.props.cfg.table.entries;

    // Clear previous error message
@@ -193,10 +224,8 @@ class CfgPageContainer extends Component {
      this.props.cfgElemSetErrMsg('Missing element name');
      return false;
    }
    if (
      configMode === CFG_ELEM_MODE_NEW &&
      this.findIndexByKeyValue(data, FIELD_NAME, name) !== -1
    ) {

    if (this.findOtherThanSelfIndexByKeyValue(data, FIELD_NAME, name, element.id) !== -1) {
      this.props.cfgElemSetErrMsg('Element name already exists');
      return false;
    }
@@ -614,6 +643,7 @@ class CfgPageContainer extends Component {
                      onNewElement={() => this.onNewElement()}
                      onSaveElement={elem => this.onSaveElement(elem)}
                      onDeleteElement={elem => this.onDeleteElement(elem)}
                      onApplyCloneElement={elem => this.onApplyCloneElement(elem)}
                      onCancelElement={() => this.onCancelElement()}
                    />
                  </Elevation>
@@ -627,6 +657,7 @@ class CfgPageContainer extends Component {
                onNewElement={() => this.onNewElement()}
                onEditElement={elem => this.onEditElement(elem)}
                onDeleteElement={() => this.onDeleteElement()}
                onApplyCloneElement={elem => this.onApplyCloneElement(elem)}
              />
            </div>
          </>
@@ -675,6 +706,7 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
  return {
    cfgElemNew: elem => dispatch(cfgElemNew(elem)),
    cfgElemClone: elem => dispatch(cfgElemClone(elem)),
    cfgElemEdit: elem => dispatch(cfgElemEdit(elem)),
    cfgElemClear: elem => dispatch(cfgElemClear(elem)),
    cfgElemSetErrMsg: msg => dispatch(cfgElemSetErrMsg(msg)),
+42 −10
Original line number Diff line number Diff line
@@ -162,6 +162,10 @@ class IDCVis extends Component {

    var groups = vis.options.groups;

    //vis.network.on("configChange", function() {
    //  console.log(network.getOptionsFromConfigurator());
    //});

    // Scenario
    createBoxGroup(groups, 'scenario', '#ffffff');
    groups.scenario.borderWidth = 4;
@@ -265,7 +269,7 @@ class IDCVis extends Component {
        if (this.props.type === TYPE_CFG) {
          this.props.onEditElement(
            table.selected.length
              ? this.getElementByName(table.entries, table.selected[0])
              ? this.getElementById(table.entries, table.selected[0])
              : null
          );
        }
@@ -282,6 +286,15 @@ class IDCVis extends Component {
    return null;
  }

  getElementById(entries, id) {
    for (var i = 0; i < entries.length; i++) {
      if (entries[i].id === id) {
        return entries[i];
      }
    }
    return null;
  }

  getTable() {
    switch (this.props.type) {
    case TYPE_CFG:
@@ -340,20 +353,39 @@ class IDCVis extends Component {
      vis.showConfig = !vis.showConfig;
    }
    vis.options.configure.enabled = vis.showConfig;
    vis.options.configure.filter = filterStr;
    vis.network.setOptions(vis.options);
    var subOptions;
    switch(vis.options.configure.filter) {
    case 'physics':
      subOptions = vis.network.getOptionsFromConfigurator(); 
      vis.options.physics = subOptions.physics;
      break;
    case 'manipulation':
      subOptions = vis.network.getOptionsFromConfigurator();
      vis.options.manipulation = subOptions.manipulation;
      break;
    case 'interaction':
      subOptions = vis.network.getOptionsFromConfigurator();
      vis.options.interaction = subOptions.interaction;
      break;
    case 'nodes':
      subOptions = vis.network.getOptionsFromConfigurator();
      vis.options.nodes = subOptions.nodes;
      break;
    case 'edges':
      subOptions = vis.network.getOptionsFromConfigurator();
      vis.options.edges = subOptions.edges;
      break;
    case 'layout':
      subOptions = vis.network.getOptionsFromConfigurator();
      vis.options.layout = subOptions.layout;
      break;   
    default:
    }

  updateConfigVisibility() {
    var vis = this.getVis();
    if (vis.options.configure) {
      vis.options.configure.enabled = this.props.devMode;
    vis.options.configure.filter = filterStr;
    vis.network.setOptions(vis.options);
  }
  }

  render() {
    this.updateConfigVisibility();
    return (
      <>
        <div
+44 −17
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ import { connect } from 'react-redux';
import React, { Component } from 'react';
import axios from 'axios';
import moment from 'moment';
import { updateObject } from '../util/object-util';
import { updateObject, deepCopy } from '../util/object-util';

// Import JS dependencies
import * as meepCtrlRestApiClient from '../../../../../js-packages/meep-ctrl-engine-client/src/index.js';
@@ -37,7 +37,11 @@ import {
  EXEC_STATE_DEPLOYED,
  NO_SCENARIO_NAME,
  VIS_VIEW,
  VIEW_NAME_NONE
  VIEW_NAME_NONE,
  PAGE_CONFIGURE,
  PAGE_EXECUTE,
  PAGE_MONITOR,
  PAGE_SETTINGS
} from '../meep-constants';

import {
@@ -45,6 +49,7 @@ import {
  createNewScenario,
  addElementToScenario,
  updateElementInScenario,
  cloneElementInScenario,
  removeElementFromScenario
} from '../util/scenario-utils';

@@ -75,13 +80,6 @@ import {
  cfgChangeTable
} from '../state/cfg';

import {
  PAGE_CONFIGURE,
  PAGE_EXECUTE,
  PAGE_MONITOR,
  PAGE_SETTINGS
} from '../meep-constants';

import { idlog } from '../util/functional';

// MEEP Controller REST API JS client
@@ -323,8 +321,12 @@ class MeepContainer extends Component {
    }, 2000);
  }

  // Change & process scenario
  changeScenario(pageType, scenario) {
    this.updateScenario(pageType, scenario, false);
  }

  // Change & process scenario
  updateScenario(pageType, scenario, reInitVisView) {
    // Change scenario state
    if (pageType === TYPE_CFG) {
      this.props.cfgChangeScenario(scenario);
@@ -345,7 +347,16 @@ class MeepContainer extends Component {

      const vis = this.props.cfgVis;
      if (vis && vis.network && vis.network.setData) {
        //save the canvas position and scale level in vis
        var view;
        if (!reInitVisView) {
          view = deepCopy(vis.network.canvas.body.view);
        }
        vis.network.setData(updatedVisData);
        if (view) {
          //restore the canvas position and scale in vis
          vis.network.canvas.body.view = view;
        }
      }
    } else {
      this.props.execChangeVisData(updatedVisData);
@@ -354,7 +365,11 @@ class MeepContainer extends Component {
      const vis = this.props.execVis;
      if (vis && vis.network && vis.network.setData) {
        _.defer(() => {
          //save the canvas position and scale level in vis
          const view = deepCopy(vis.network.canvas.body.view);
          vis.network.setData(this.props.execVisData);
          //restore the canvas position and scale in vis
          vis.network.canvas.body.view = view;
        });
      }
    }
@@ -363,18 +378,18 @@ class MeepContainer extends Component {
  // Create, store & process new scenario
  createScenario(pageType, name) {
    var scenario = createNewScenario(name);
    this.changeScenario(pageType, scenario);
    this.updateScenario(pageType, scenario, true);
  }

  // Set & process scenario
  setScenario(pageType, scenario) {
    this.changeScenario(pageType, scenario);
    this.updateScenario(pageType, scenario, true);
  }

  // Delete & process scenario
  deleteScenario(pageType) {
    var scenario = createNewScenario(NO_SCENARIO_NAME);
    this.changeScenario(pageType, scenario);
    this.updateScenario(pageType, scenario, true);
  }

  // Refresh Active scenario
@@ -385,15 +400,17 @@ class MeepContainer extends Component {
  }

  // Add new element to scenario
  newScenarioElem(pageType, element) {
  newScenarioElem(pageType, element, scenarioUpdate) {
    var scenario =
      pageType === TYPE_CFG
        ? this.props.cfg.scenario
        : this.props.exec.scenario;
    var updatedScenario = updateObject({}, scenario);
    addElementToScenario(updatedScenario, element);
    if (scenarioUpdate) {
      this.changeScenario(pageType, updatedScenario);
    }
  }

  // Update element in scenario
  updateScenarioElem(pageType, element) {
@@ -417,6 +434,13 @@ class MeepContainer extends Component {
    this.changeScenario(pageType, updatedScenario);
  }

  // Clone element in scenario
  cloneScenarioElem(element) {
    var updatedScenario = updateObject({}, this.props.cfg.scenario);
    cloneElementInScenario(updatedScenario, element, this.props.cfg.table);
    this.changeScenario(TYPE_CFG, updatedScenario);
  }

  renderPage() {
    switch (this.props.page) {
    case PAGE_CONFIGURE:
@@ -433,8 +457,11 @@ class MeepContainer extends Component {
          deleteScenario={() => {
            this.deleteScenario(TYPE_CFG);
          }}
          newScenarioElem={elem => {
            this.newScenarioElem(TYPE_CFG, elem);
          newScenarioElem={(elem, update) => {
            this.newScenarioElem(TYPE_CFG, elem, update);
          }}
          cloneScenarioElem={elem => {
            this.cloneScenarioElem(elem);
          }}
          updateScenarioElem={elem => {
            this.updateScenarioElem(TYPE_CFG, elem);
+2 −1
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ export const CFG_BTN_IMP_SCENARIO = 'cfg-btn-imp-scenario';
export const CFG_BTN_EXP_SCENARIO = 'cfg-btn-exp-scenario';
export const CFG_BTN_NEW_ELEM = 'cfg-btn-new-elem';
export const CFG_BTN_DEL_ELEM = 'cfg-btn-del-elem';
export const CFG_BTN_CLONE_ELEM = 'cfg-btn-clone-elem';
export const CFG_BTN_SAVE_ELEM = 'cfg-btn-save-elem';

export const CFG_ELEM_TYPE = 'cfg-elem-type';
@@ -157,7 +158,7 @@ export const DEFAULT_PACKET_LOSS_INTRA_ZONE = 0;
export const DEFAULT_LATENCY_TERMINAL_LINK = 1;
export const DEFAULT_LATENCY_JITTER_TERMINAL_LINK = 1;
export const DEFAULT_THROUGHPUT_TERMINAL_LINK = 1000;
export const DEFAULT_PACKET_LOSS_TERMINAL_LINK = 1;
export const DEFAULT_PACKET_LOSS_TERMINAL_LINK = 0;
export const DEFAULT_LATENCY_LINK = 0;
export const DEFAULT_LATENCY_JITTER_LINK = 0;
export const DEFAULT_THROUGHPUT_LINK = 1000;
Loading