Commit 05a89281 authored by Kevin Di Lallo's avatar Kevin Di Lallo
Browse files

version information in frontend + network termination monitoring

parent bb7a5974
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -2,6 +2,10 @@
 * Copyright (c) 2020 ETSI.  All rights reserved.
 */

// Version
export const MEC_SANDBOX_VERSION = 'v1.6';

// Host
export const HOST_PATH = location.origin;

// Pages
@@ -57,7 +61,7 @@ export const DIALOG_SESSION_TERMINATED = 'DIALOG_SESSION_TERMINATED';
export const DIALOG_HELP_GETTING_STARTED = 'DIALOG_HELP_GETTING_STARTED';
export const DIALOG_CONFIRM_DELETE_APP = 'DIALOG_CONFIRM_DELETE_APP';
export const DIALOG_CREATE_NEW_APP = 'DIALOG_CREATE_NEW_APP';
export const DIALOG_NEWS = 'DIALOG_NEWS';
export const DIALOG_VERSION = 'DIALOG_VERSION';

// Alert Types
export const ALERT_DEGRADED_NETWORK = 'ALERT_DEGRADED_NETWORK';
+0 −75
Original line number Diff line number Diff line
/*
 * Copyright (c) 2020 ETSI.  All rights reserved.
 */

import React, { Component } from 'react';
import BasicDialog from './basic-dialog';
import { Typography } from '@rmwc/typography';
import {
  WIKI_HELP_URL,
  WIKI_DISCUSSION_BOARD_URL
} from '../../app-constants';

class NewsDialog extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <BasicDialog
        title={this.props.title}
        open={this.props.open}
        onSubmit={this.props.onClose}
        onClose={this.props.onClose}
        submitLabel = {'Ok'}
      >
        <div style={styles.text}>
          <Typography theme="primary" use="body1">
            <p><b>November 2021</b></p>
          </Typography>
          <Typography use="body1">
            WebSocket support for subscriptions per MEC009:
            <ul>
              <li>MEC028 - WLAN Access Information API (v2.2.1)</li>
            </ul>
          </Typography>
          <Typography theme="primary" use="body1">
            <p><b>October 2021</b></p>
          </Typography>
          <Typography use="body1">
            The following APIs are supported as Beta features:
            <ul>
              <li>MEC011 - Edge Platform Application Enablement (v2.1.1)</li>
              <li>MEC021 - Application Mobility Service API (v2.1.1)</li>
            </ul>
            <p>
              Usage details are available in the <a href={WIKI_HELP_URL} target="_blank">MEC Sandbox Wiki</a><br/>
              Questions & feedback are appreciated on the <a href={WIKI_DISCUSSION_BOARD_URL} target="_blank">Slack Discussion Board</a>
            </p>
            <p>
              Thank you in advance for your collaboration in trying out these new features.
            </p>
            <p style={styles.signature}>
              The MEC Sandbox Team
            </p>
          </Typography>
        </div>

      </BasicDialog>
    );
  }
}

const styles = {
  text: {
    // color: 'black',
    marginLeft: 10,
    marginRight: 15
  },
  signature: {
    textAlign: 'right'
  }
};

export default NewsDialog;
+137 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2020 ETSI.  All rights reserved.
 */

import React, { Component } from 'react';
import BasicDialog from './basic-dialog';
import { Typography } from '@rmwc/typography';

class VersionDialog extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <BasicDialog
        title={this.props.title}
        open={this.props.open}
        onSubmit={this.props.onClose}
        onClose={this.props.onClose}
        submitLabel = {'Ok'}
      >
        <div style={styles.text}>

          <Typography theme="primary" use="subtitle1"><p><b>v1.6 &bull;</b> 2021-12-16</p></Typography>
          <Typography use="body1">
            Final STF599 update:
            <ul>
              <li>AdvantEDGE baseline upgrade to v1.8.1</li>
              <li>General maintenance fixes</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.5.3 (beta) &bull;</b> 2021-11-22</p></Typography>
          <Typography use="body1">
            Maintenance Release:
            <ul>
              <li>Service IDs displayed in frontend</li>
              <li>Garbage collector for internal databases</li>
              <li>User-persistent sandbox names</li>
              <li>MEC Service bug fixes</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.5.2 (beta) &bull;</b> 2021-11-08</p></Typography>
          <Typography use="body1">
            MEC028 WebSockets:
            <ul>
              <li>MEC028 subscriptions - support for MEC009 WebSocket fallback pattern</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.5.1 (beta) &bull;</b> 2021-10-30</p></Typography>
          <Typography use="body1">
            MEC API Maintenance:
            <ul>
              <li>MEC Service bug fixes</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.5 (beta) &bull;</b> 2021-09-30</p></Typography>
          <Typography use="body1">
            New MEC APIs:
            <ul>
              <li>MEC011 - Edge Platform Application Enablement</li>
              <li>MEC021 - Application Mobility Service</li>
              <li>Dual MEP network to showcase new APIs</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.4.1 &bull;</b> 2021-08-06</p></Typography>
          <Typography use="body1">
            MEC Service Maintenance:
            <ul>
              <li>MEC Service bug fixes</li>
              <li>Beta tags removed from API endpoints</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.4 (beta) &bull;</b> 2021-07-20</p></Typography>
          <Typography use="body1">
            MEC Service Enhancements:
            <ul>
              <li>MEC012, MEC013 & MEC028 support for all API endpoints</li>
              <li>MEC Service bug fixes</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.3 &bull;</b> 2021-06-03</p></Typography>
          <Typography use="body1">
            Long-term Sandbox Analytics Storage:
            <ul>
              <li>Metrics object storage deployment</li>
              <li>Thanos integration for metrics data long-term storage</li>
              <li>Maintenance fixes</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.2 &bull;</b> 2021-05-11</p></Typography>
          <Typography use="body1">
            MEC Sandbox VM Upgrade:
            <ul>
              <li>CPU/Memory upgrade</li>
              <li>Maintenance fixes</li>
            </ul>
          </Typography>

          <Typography theme="primary" use="subtitle1"><p><b>v1.1 &bull;</b> 2021-03-23</p></Typography>
          <Typography use="body1">
            Monitoring & Analytics:
            <ul>
              <li>Session statistics</li>
              <li>MEC Service & Platform API metrics</li>
              <li>Alerting framework</li>
              <li>Maintenance fixes</li>
            </ul>
          </Typography>

        </div>

      </BasicDialog>
    );
  }
}

const styles = {
  text: {
    // color: 'black',
    marginLeft: 10,
    marginRight: 15
  },
  signature: {
    textAlign: 'right'
  }
};

export default VersionDialog;
+105 −11
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ import SessionTerminatedDialog from '../components/dialogs/session-terminated-di
import HelpGettingStartedDialog from '../components/dialogs/help-getting-started-dialog';
import ConfirmDeleteAppDialog from '../components/dialogs/confirm-delete-app-dialog';
import CreateNewAppDialog from '../components/dialogs/create-new-app-dialog';
import NewsDialog from '../components/dialogs/news-dialog';
import VersionDialog from '../components/dialogs/version-dialog';

import * as meepAuthSvcRestApiClient from '../../../../../js-packages/meep-auth-svc-client/src/index.js';
import * as meepPlatformCtrlRestApiClient from '../../../../../js-packages/meep-platform-ctrl-client/src/index.js';
@@ -45,7 +45,7 @@ import {
  DIALOG_HELP_GETTING_STARTED,
  DIALOG_CONFIRM_DELETE_APP,
  DIALOG_CREATE_NEW_APP,
  DIALOG_NEWS,
  DIALOG_VERSION,
  ALERT_DEGRADED_NETWORK,
  STATUS_SIGNED_IN,
  STATUS_SIGNING_IN,
@@ -86,6 +86,7 @@ import {
  uiSandboxChangeUpdateUeInProgressCount,
  uiSandboxChangeUpdateAutomationInProgressCount,
  uiSandboxChangeUpdateAppInstancesInProgressCount,
  uiSandboxChangeTerminationInProgressCount,
  uiSandboxChangeActivationInProgressCount,
  uiSandboxChangeActivationInProgressScenarioName
} from '../state/ui';
@@ -169,6 +170,9 @@ class AppContainer extends Component {
    this.meepMonEngineApi = new meepMonEngineRestApiClient.PodStatesApi();

    // Initialize undefined states
    if (this.props.terminationInProgressCount === undefined) {
      this.props.changeTerminationInProgressCount(-1);
    }
    if (this.props.activationInProgressCount === undefined) {
      this.props.changeActivationInProgressCount(-1);
    }
@@ -301,6 +305,7 @@ class AppContainer extends Component {
          // Refresh sandbox page
          if (this.props.page === PAGE_SANDBOX) {
            if (this.props.sandboxName) {
              this.checkScenarioState();
              this.refreshScenario();
              this.refreshMap();
              // Only update while scenario is running
@@ -597,6 +602,7 @@ class AppContainer extends Component {
    this.props.changeSignInStatus(STATUS_SIGNED_OUT);
    this.props.changeSandboxName('');
    this.updateScenario(null);
    this.props.changeTerminationInProgressCount(-1);
    this.props.changeActivationInProgressCount(-1);
    this.props.changeActivationInProgressScenarioName('');
    this.props.changeMap({ueList: [], poaList: [], computeList: []});
@@ -620,7 +626,7 @@ class AppContainer extends Component {
   * @param {module:model/GetStates} data The data returned by the service call.
   * @param {String} response The complete HTTP response.
   */
  getStatesCb(error, data, response) {
  getSandboxStateCb(error, data) {
    if (error) {
      if (error.status === StatusCodes.UNAUTHORIZED) {
        this.stopSandboxCreationTimer();
@@ -629,11 +635,13 @@ class AppContainer extends Component {
      }
      return;
    }
    for (var i in response.body.podStatus) {
      if (response.body.podStatus[i].logicalState !== 'Running') {
    if (data) {
      for (var i in data.podStatus) {
        if (data.podStatus[i].logicalState !== 'Running') {
          return;
        }
      }
    }

    // all pods runnings, sandbox is ready
    this.stopSandboxCreationTimer();
@@ -662,7 +670,50 @@ class AppContainer extends Component {
      };

      this.meepMonEngineApi.getStates(queryParams, (error, data, response) => {
        this.getStatesCb(error, data, response);
        this.getSandboxStateCb(error, data, response);
      });
    }
  }

  /**
   * Callback function to receive the result of the getStates operation.
   * @callback module:api/MonEngineApi~getStatesCallback
   * @param {String} error Error message, if any.
   * @param {module:model/GetStates} data The data returned by the service call.
   * @param {String} response The complete HTTP response.
   */
  getScenarioStateCb(error, data) {
    if (error) {
      return;
    }

    // Termination is complete when all pods are no longer in Running state
    if (data) {
      for (var i in data.podStatus) {
        if (data.podStatus[i].logicalState === 'Running') {
          return;
        }
      }
    }
    this.props.changeTerminationInProgressCount(-1);
  }

  checkScenarioState() {
    // Only check scenario state during termination
    if (this.props.terminationInProgressCount === -1) {
      return;
    }

    var sandboxName = this.props.sandboxName;
    if (sandboxName !== '') {
      var queryParams = {
        'type': 'scenario',
        'sandbox': sandboxName,
        'long': false
      };

      this.meepMonEngineApi.getStates(queryParams, (error, data, response) => {
        this.getScenarioStateCb(error, data, response);
      });
    }
  }
@@ -715,7 +766,7 @@ class AppContainer extends Component {
   * @param {module:model/Scenario} data The data returned by the service call.
   */
  getActiveScenarioCb(error, data) {
    if ((error !== null) || (!data.deployment)) {
    if (error !== null) {
      if (error.status === 404) {
        this.updateScenario(null);
        this.props.changeApiTable(null);
@@ -726,6 +777,11 @@ class AppContainer extends Component {
      return;
    }

    // Validate scenario
    if (data && !data.deployment) {
      return;
    }

    // Store & Process deployed scenario
    if (this.props.activationInProgressCount === -1) {
      this.updateScenario(data);
@@ -754,6 +810,39 @@ class AppContainer extends Component {
    );
  }

  checkScenarioStatus() {
    // // Scenario pods
    // axios
    //   .get(`${basepathMonEngine}/states?long=true&type=scenario&sandbox=${this.props.sandbox}`)
    //   .then(res => {
    //     var scenarioPodsPhases = res.data.podStatus;

    //     // Add node ID
    //     if (this.props.exec && this.props.exec.table && this.props.exec.table.entries) {
    //       for (var i in scenarioPodsPhases) {
    //         var scenarioPod = scenarioPodsPhases[i];
    //         var elem = getElemByName(this.props.exec.table.entries, scenarioPod.name);
    //         scenarioPodsPhases[i].id = elem ? elem.id : '';
    //       }
    //     }
    //     this.props.changeScenarioPodsPhases(scenarioPodsPhases);
    //   })
    //   .catch(() => {
    //     this.props.changeScenarioPodsPhases([]);
    //   });

    // // Service maps
    // axios
    //   .get(`${basepathSandboxCtrl}/active/serviceMaps`)
    //   .then(res => {
    //     var serviceMaps = res.data;
    //     this.props.changeServiceMaps(serviceMaps);
    //   })
    //   .catch(() => {
    //     this.props.changeServiceMaps([]);
    //   });
  }

  syncUeButtons(mapUeList) {
    var nbHv = 0, nbLv = 0, nbZv = 0;
    var found = false;
@@ -1175,6 +1264,9 @@ class AppContainer extends Component {

  decrementProgressCounters() {
    // Update counters
    if (this.props.terminationInProgressCount !== -1) {
      this.props.changeTerminationInProgressCount(this.props.terminationInProgressCount - 1);
    }
    if (this.props.activationInProgressCount !== -1) {
      this.props.changeActivationInProgressCount(this.props.activationInProgressCount - 1);
    }
@@ -1287,9 +1379,9 @@ class AppContainer extends Component {
          }}
          mepNames={this.props.mepList}
        />
        <NewsDialog
          title='MEC Sandbox Beta Features'
          open={this.props.currentDialog === DIALOG_NEWS}
        <VersionDialog
          title='MEC Sandbox Versions'
          open={this.props.currentDialog === DIALOG_VERSION}
          onClose={() => {
            this.closeDialog();
          }}
@@ -1362,6 +1454,7 @@ const mapStateToProps = state => {
    svcTable: state.sbox.svcTable.data,
    updateUeInProgressCount: state.ui.updateUeInProgressCount,
    updateAppInstancesInProgressCount: state.ui.updateAppInstancesInProgressCount,
    terminationInProgressCount: state.ui.terminationInProgressCount,
    activationInProgressCount: state.ui.activationInProgressCount,
    activationInProgressScenarioName: state.ui.activationInProgressScenarioName,
    map: state.sbox.map,
@@ -1407,6 +1500,7 @@ const mapDispatchToProps = dispatch => {
    changeUpdateUeInProgressCount: count => dispatch(uiSandboxChangeUpdateUeInProgressCount(count)),
    changeUpdateAutomationInProgressCount: count => dispatch(uiSandboxChangeUpdateAutomationInProgressCount(count)),
    changeUpdateAppInstancesInProgressCount: count => dispatch(uiSandboxChangeUpdateAppInstancesInProgressCount(count)),
    changeTerminationInProgressCount: count => dispatch(uiSandboxChangeTerminationInProgressCount(count)),
    changeActivationInProgressCount: count => dispatch(uiSandboxChangeActivationInProgressCount(count)),
    changeActivationInProgressScenarioName: name => dispatch(uiSandboxChangeActivationInProgressScenarioName(name))
  };
+222 −183
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import {
  uiSandboxChangeUpdateUeInProgressCount,
  uiSandboxChangeUpdateAutomationInProgressCount,
  uiSandboxChangeUpdateAppInstancesInProgressCount,
  uiSandboxChangeTerminationInProgressCount,
  uiSandboxChangeActivationInProgressCount,
  uiSandboxChangeActivationInProgressScenarioName
} from '../../state/ui';
@@ -81,7 +82,8 @@ class ConfigPane extends Component {
    }
  }

  componentDidUpdate() {
  componentDidUpdate(prevProps) {
    // Get configured scenario if activated by another browser
    if (this.props.activationInProgressCount === -1) {
      if (this.props.scenario !== null && this.props.scenario.name !== NO_SCENARIO_NAME) {
        if (this.props.networkFileSelected !== this.props.scenario.name) {
@@ -89,6 +91,24 @@ class ConfigPane extends Component {
        }
      }
    }

    // On termination completion, check if a scenario requires activation
    if (this.props.terminationInProgressCount === -1 &&
      this.props.terminationInProgressCount !== prevProps.terminationInProgressCount &&
      this.props.networkFileSelected !== DEFAULT_NO_NETWORK_FILE_SELECTED) {
      this.props.activateApi.activateScenario(this.props.networkFileSelected, null, (error, data, response) => {
        this.activateScenarioCb(error, data, response);
      });
      this.getScenario(this.props.networkFileSelected, true);
    }

    // Reset config pane if scenario was terminated by another source
    if (this.props.activationInProgressCount === -1) {
      if (this.props.scenario === null && prevProps.scenario !== null) {
        this.resetConfigPane();
        this.resetApiPane();
      }
    }
  }

  decrement(value) {
@@ -160,6 +180,12 @@ class ConfigPane extends Component {
    this.updateAutomation(false);
  }

  terminateScenarioCb(error) {
    if (error) {
      return;
    }
  }

  setScenario(name) {
    // Reset config & api panes
    this.resetConfigPane();
@@ -172,48 +198,29 @@ class ConfigPane extends Component {

    if (name !== DEFAULT_NO_NETWORK_FILE_SELECTED) {
      if (this.props.scenario) {
        //if a scenario already is running
        this.props.changeActivationInProgressCount(5);
        this.terminateAndActivateScenario(name);
        this.props.changeActivationInProgressCount(10);
        // Terminate running scenario before acivating new scenario
        // NOTE: new scenario will be activated when old one has been fully removed
        this.props.changeTerminationInProgressCount(5);
        this.props.activateApi.terminateScenario((error, data, response) => {
          this.terminateScenarioCb(error, data, response);
        });
      } else {
        //if no scenario running
        // Activate scenario immediately if no scenario running
        this.props.activateApi.activateScenario(name, null, (error, data, response) => {
          this.activateScenarioCb(error, data, response);
        });
        this.getScenario(name, true);
      }
    } else {
      //scenario termination
      // Terminate scenario
      this.props.changeTerminationInProgressCount(5);
      this.props.activateApi.terminateScenario((error, data, response) => {
        this.terminateScenarioCb(error, data, response);
      });
    }
  }

  terminateAndActivateScenarioCb(error) {
    if (error) {
      return;
    }
    this.props.activateApi.activateScenario(this.name, null, (error, data, response) => {
      this.activateScenarioCb(error, data, response);
    });
    this.getScenario(this.name, true);
  }

  terminateAndActivateScenario(name) {
    this.name = name;
    this.props.activateApi.terminateScenario((error, data, response) => {
      this.terminateAndActivateScenarioCb(error, data, response);
    });
  }

  terminateScenarioCb(error) {
    this.activationInProgress = false;
    if (error) {
      return;
    }
  }

  /**
   * Callback function to receive the result of the getScenario operation.
   * @callback module:api/ScenarioExecutionApi~getScenarioCallback
@@ -449,7 +456,9 @@ class ConfigPane extends Component {
      return null;
    }

    const configEnabled = (this.props.networkFileSelected !== DEFAULT_NO_NETWORK_FILE_SELECTED) ? true : false;
    const terminationInProgress = (this.props.terminationInProgressCount !== -1) ? true : false;
    const networkFileSelected = (this.props.networkFileSelected !== DEFAULT_NO_NETWORK_FILE_SELECTED) ? true : false;
    const configEnabled = (networkFileSelected && !terminationInProgress) ? true : false;
    const networkFileSelectionEnabled = (this.props.activationInProgressCount === -1) ? true : false;
    const maxNbStationaryUe = (this.props.stationaryUeList) ? this.props.stationaryUeList.length : 0;
    const maxNbLowVelocityUe = (this.props.lowVelocityUeList) ? this.props.lowVelocityUeList.length : 0;
@@ -470,7 +479,7 @@ class ConfigPane extends Component {
          <Typography theme="primary" use="headline6">Configuration</Typography>
        </div>

        <Grid style={{ marginBottom: 30}}>
        <Grid>
          <GridCell span={12}>
            <Select
              title="Select the network to simulate"
@@ -489,8 +498,9 @@ class ConfigPane extends Component {
          </GridCell>
        </Grid>

        {!terminationInProgress &&
          <div style={!configEnabled ? {pointerEvents: 'none', opacity: '0.3', marginLeft: 5} : { marginLeft: 5 }}>
          <Grid style={{ marginBottom: 20}}>
            <Grid style={{ marginTop: 30, marginBottom: 20}}>
              <GridCell span={5}>
                <Typography use="body1">Pause</Typography>
              </GridCell>
@@ -648,6 +658,33 @@ class ConfigPane extends Component {

            <AppInstanceTable/>
          </div>
        }

        {networkFileSelected && terminationInProgress &&
          <Grid>
            <GridCell span={12}>
              <div style={{ marginTop: 15 }}>
                <Typography theme="primary" use="body1">
                  Network activation in progress...<br/>
                  This operation takes a few seconds. <br/>
                </Typography>
              </div>
            </GridCell>
          </Grid>
        }

        {!networkFileSelected && terminationInProgress &&
          <Grid>
            <GridCell span={12}>
              <div style={{ marginTop: 15 }}>
                <Typography theme="primary" use="body1">
                  Network termination in progress...<br/>
                  This operation takes a few seconds.
                </Typography>
              </div>
            </GridCell>
          </Grid>
        }

        <div hidden={!this.props.currentAlert} style={{ width: '100%', borderTop: '2px solid #e4e4e4', marginTop: 30, marginBottom: 10}} />

@@ -731,6 +768,7 @@ const mapStateToProps = state => {
    appInstanceTable: state.sbox.appInstanceTable.data,
    scenario: state.sbox.scenario,
    appInstancesSelected: state.ui.appInstancesSelected,
    terminationInProgressCount: state.ui.terminationInProgressCount,
    activationInProgressCount: state.ui.activationInProgressCount,
    activationInProgressScenarioName: state.ui.activationInProgressScenarioName
  };
@@ -739,6 +777,7 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
  return {
    changeCurrentDialog: type => dispatch(uiChangeCurrentDialog(type)),
    changeTerminationInProgressCount: count => dispatch(uiSandboxChangeTerminationInProgressCount(count)),
    changeActivationInProgressCount: count => dispatch(uiSandboxChangeActivationInProgressCount(count)),
    changeActivationInProgressScenarioName: name => dispatch(uiSandboxChangeActivationInProgressScenarioName(name)),
    changeUpdateUeInProgressCount: count => dispatch(uiSandboxChangeUpdateUeInProgressCount(count)),
Loading