Loading js-apps/frontend/src/js/containers/home/home-page-container.js +8 −0 Changes for js-apps/frontend/src/js/containers/home/home-page-container.js: 8 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -75,6 +75,14 @@ class HomePageContainer extends Component { EdgeNative - Connector provides also a support for Edge Native Connector (3GPP TS 29.222: 3rd Generation Partnership Project; Technical Specification Group Core Network and Terminals; Common API Framework for 3GPP Northbound APIs). </p> <p> EdgeNative - Connector provides the user with two network simulation mechanisms to reproduce the behaviour of these 4G, 5G and Wi-Fi scenarios. The first mechanism is the built-in network characteristics emulation, which applies configurable latency, jitter, packet loss and throughput to the connectivity between terminals, points of access and MEC hosts, enabling rapid experimentation without an external radio stack. The second mechanism is an external radio network emulator based on Simu5G/SimuLTE, optionally coupled with SUMO/Veins for terminal mobility, so that user traffic and Radio Network Information (MEC012) can reflect a simulated radio access network rather than purely configured link parameters. In both cases, the ETSI MEC Service APIs remain the same, allowing the user to compare application behaviour under emulated and radio-simulated conditions. </p> <p> Such contextual information can offer significant differential performance for edge based MEC applications. EdgeNative - Connector also provides the support of capabilities described by Edge Platform Application Enablement (MEC011) and Device Application Interface (MEC016). Loading js-apps/frontend/src/js/containers/sandbox/configuration-pane.js +54 −2 Changes for js-apps/frontend/src/js/containers/sandbox/configuration-pane.js: 54 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -32,6 +32,8 @@ import { import { uiChangeCurrentDialog, uiChangeCurrentAlert, uiChangeDialogErrorMsg, uiSandboxChangeNetworkFileSelected, uiSandboxChangeNetworkInfo, uiSandboxChangePauseButton, Loading Loading @@ -65,6 +67,8 @@ import { getNetworkInfo } from '../../util/scenario-utils'; import { isNemUnreachable } from '../../util/nem-error'; const HIGH_VELOCITY_UE = 'hv'; const LOW_VELOCITY_UE = 'lv'; const STATIONARY_UE = 'zv'; Loading @@ -75,10 +79,13 @@ const ACTION_REMOVE = 'remove'; class ConfigPane extends Component { constructor(props) { super(props); this.ignoreNetworkSelectChange = true; this.networkSelectArmTimer = null; } componentDidMount() { log.info('componentDidMount: Configuration Pane mounted'); this.armNetworkSelect(); if (this.props.scenario !== null && this.props.scenario.name !== NO_SCENARIO_NAME) { this.getScenario(this.props.scenario.name, false); } else { Loading @@ -87,7 +94,32 @@ class ConfigPane extends Component { } } componentWillUnmount() { if (this.networkSelectArmTimer) { clearTimeout(this.networkSelectArmTimer); this.networkSelectArmTimer = null; } } /** * Ignore Select onChange until options are stable. RMWC Select fires a * synthetic change when the scenario list arrives after sign-in. */ armNetworkSelect() { this.ignoreNetworkSelectChange = true; if (this.networkSelectArmTimer) { clearTimeout(this.networkSelectArmTimer); } this.networkSelectArmTimer = setTimeout(() => { this.ignoreNetworkSelectChange = false; this.networkSelectArmTimer = null; }, 300); } componentDidUpdate(prevProps) { if (prevProps.networkFiles !== this.props.networkFiles) { this.armNetworkSelect(); } // Get configured scenario if activated by another browser if (this.props.activationInProgressCount === -1 && this.props.terminationInProgressCount === -1) { if (this.props.scenario !== null && this.props.scenario.name !== NO_SCENARIO_NAME) { Loading Loading @@ -177,10 +209,19 @@ class ConfigPane extends Component { * @callback module:api/ScenarioExecutionApi~activateScenarioCallback * @param {String} error Error message, if any. */ activateScenarioCb(error/*, data*/) { activateScenarioCb(error, _data, response) { if (error) { this.props.changeActivationInProgressCount(-1); this.props.changeActivationInProgressScenarioName(''); this.resetConfigPane(); this.resetApiPane(); if (isNemUnreachable(error, response)) { this.props.changeCurrentAlert(ALERT_NEM_NOT_RUNNING); } return; } this.props.changeCurrentAlert(null); this.props.changeDialogErrorMsg(''); // Enable movement by default this.updateAutomation(false); } Loading @@ -192,7 +233,12 @@ class ConfigPane extends Component { } setScenario(name) { if (!name || name === this.props.networkFileSelected) { return; } log.info(`setScenario: Setting new scenario network: ${name}`); this.props.changeCurrentAlert(null); this.props.changeDialogErrorMsg(''); // Reset config & api panes this.resetConfigPane(); this.resetApiPane(); Loading Loading @@ -497,9 +543,12 @@ class ConfigPane extends Component { disabled={!networkFileSelectionEnabled} options={this.props.networkFiles} onChange={event => { if (this.ignoreNetworkSelectChange) { return; } this.setScenario(event.target.value); }} value={this.props.networkFileSelected} value={this.props.networkFileSelected || DEFAULT_NO_NETWORK_FILE_SELECTED} /> {this.props.currentAlert === ALERT_NEM_NOT_RUNNING && <Typography use="subtitle2" style={styles.nemErrorText}> Loading Loading @@ -771,6 +820,7 @@ const mapStateToProps = state => { return { page: state.ui.page, currentAlert: state.ui.currentAlert, dialogErrorMsg: state.ui.dialogErrorMsg, networkFiles: state.ui.networkFiles, networkFileSelected: state.ui.networkFileSelected, networkInfo: state.ui.networkInfo, Loading @@ -794,6 +844,8 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { changeCurrentDialog: type => dispatch(uiChangeCurrentDialog(type)), changeCurrentAlert: alert => dispatch(uiChangeCurrentAlert(alert)), changeDialogErrorMsg: msg => dispatch(uiChangeDialogErrorMsg(msg)), changeTerminationInProgressCount: count => dispatch(uiSandboxChangeTerminationInProgressCount(count)), changeActivationInProgressCount: count => dispatch(uiSandboxChangeActivationInProgressCount(count)), changeActivationInProgressScenarioName: name => dispatch(uiSandboxChangeActivationInProgressScenarioName(name)), Loading js-apps/frontend/src/js/util/nem-error.js 0 → 100644 +63 −0 Changes for js-apps/frontend/src/js/util/nem-error.js: 63 added lines, 0 removed lines. Original line number Diff line number Diff line import { StatusCodes } from 'http-status-codes'; import { MSG_NEM_NOT_RUNNING } from '../app-constants'; /** * HTTP status from a SuperAgent error or response. * @param {Object} error * @param {Object} response * @return {number|undefined} */ export function nemHttpStatus(error, response) { if (error && typeof error.status === 'number') { return error.status; } if (response && typeof response.status === 'number') { return response.status; } return undefined; } /** * Best-effort text of an HTTP error body. Empty when SuperAgent did not * expose a body (do not invent a "failed to start" fallback). * @param {Object} error * @param {Object} response * @return {string} */ export function nemErrorText(error, response) { const candidates = [ response && response.text, response && response.body && response.body.error, response && typeof response.body === 'string' && response.body, error && error.response && error.response.text, error && error.response && error.response.body ]; for (let i = 0; i < candidates.length; i++) { const v = candidates[i]; if (typeof v === 'string' && v.trim()) { return v.trim(); } } return ''; } /** * True when NEM HTTP was not reached. Activate application errors (NEM 500, * sudo, INACTIVE) must not match, even if the controller wrapped them as 502. * @param {Object} error * @param {Object} response * @return {boolean} */ export function isNemUnreachable(error, response) { const status = nemHttpStatus(error, response); if (status === StatusCodes.GATEWAY_TIMEOUT || status === StatusCodes.REQUEST_TIMEOUT || status === 0) { return true; } if (status === StatusCodes.BAD_GATEWAY) { const text = nemErrorText(error, response); return text === MSG_NEM_NOT_RUNNING || text.indexOf(MSG_NEM_NOT_RUNNING) !== -1; } return false; } Loading
js-apps/frontend/src/js/containers/home/home-page-container.js +8 −0 Changes for js-apps/frontend/src/js/containers/home/home-page-container.js: 8 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -75,6 +75,14 @@ class HomePageContainer extends Component { EdgeNative - Connector provides also a support for Edge Native Connector (3GPP TS 29.222: 3rd Generation Partnership Project; Technical Specification Group Core Network and Terminals; Common API Framework for 3GPP Northbound APIs). </p> <p> EdgeNative - Connector provides the user with two network simulation mechanisms to reproduce the behaviour of these 4G, 5G and Wi-Fi scenarios. The first mechanism is the built-in network characteristics emulation, which applies configurable latency, jitter, packet loss and throughput to the connectivity between terminals, points of access and MEC hosts, enabling rapid experimentation without an external radio stack. The second mechanism is an external radio network emulator based on Simu5G/SimuLTE, optionally coupled with SUMO/Veins for terminal mobility, so that user traffic and Radio Network Information (MEC012) can reflect a simulated radio access network rather than purely configured link parameters. In both cases, the ETSI MEC Service APIs remain the same, allowing the user to compare application behaviour under emulated and radio-simulated conditions. </p> <p> Such contextual information can offer significant differential performance for edge based MEC applications. EdgeNative - Connector also provides the support of capabilities described by Edge Platform Application Enablement (MEC011) and Device Application Interface (MEC016). Loading
js-apps/frontend/src/js/containers/sandbox/configuration-pane.js +54 −2 Changes for js-apps/frontend/src/js/containers/sandbox/configuration-pane.js: 54 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -32,6 +32,8 @@ import { import { uiChangeCurrentDialog, uiChangeCurrentAlert, uiChangeDialogErrorMsg, uiSandboxChangeNetworkFileSelected, uiSandboxChangeNetworkInfo, uiSandboxChangePauseButton, Loading Loading @@ -65,6 +67,8 @@ import { getNetworkInfo } from '../../util/scenario-utils'; import { isNemUnreachable } from '../../util/nem-error'; const HIGH_VELOCITY_UE = 'hv'; const LOW_VELOCITY_UE = 'lv'; const STATIONARY_UE = 'zv'; Loading @@ -75,10 +79,13 @@ const ACTION_REMOVE = 'remove'; class ConfigPane extends Component { constructor(props) { super(props); this.ignoreNetworkSelectChange = true; this.networkSelectArmTimer = null; } componentDidMount() { log.info('componentDidMount: Configuration Pane mounted'); this.armNetworkSelect(); if (this.props.scenario !== null && this.props.scenario.name !== NO_SCENARIO_NAME) { this.getScenario(this.props.scenario.name, false); } else { Loading @@ -87,7 +94,32 @@ class ConfigPane extends Component { } } componentWillUnmount() { if (this.networkSelectArmTimer) { clearTimeout(this.networkSelectArmTimer); this.networkSelectArmTimer = null; } } /** * Ignore Select onChange until options are stable. RMWC Select fires a * synthetic change when the scenario list arrives after sign-in. */ armNetworkSelect() { this.ignoreNetworkSelectChange = true; if (this.networkSelectArmTimer) { clearTimeout(this.networkSelectArmTimer); } this.networkSelectArmTimer = setTimeout(() => { this.ignoreNetworkSelectChange = false; this.networkSelectArmTimer = null; }, 300); } componentDidUpdate(prevProps) { if (prevProps.networkFiles !== this.props.networkFiles) { this.armNetworkSelect(); } // Get configured scenario if activated by another browser if (this.props.activationInProgressCount === -1 && this.props.terminationInProgressCount === -1) { if (this.props.scenario !== null && this.props.scenario.name !== NO_SCENARIO_NAME) { Loading Loading @@ -177,10 +209,19 @@ class ConfigPane extends Component { * @callback module:api/ScenarioExecutionApi~activateScenarioCallback * @param {String} error Error message, if any. */ activateScenarioCb(error/*, data*/) { activateScenarioCb(error, _data, response) { if (error) { this.props.changeActivationInProgressCount(-1); this.props.changeActivationInProgressScenarioName(''); this.resetConfigPane(); this.resetApiPane(); if (isNemUnreachable(error, response)) { this.props.changeCurrentAlert(ALERT_NEM_NOT_RUNNING); } return; } this.props.changeCurrentAlert(null); this.props.changeDialogErrorMsg(''); // Enable movement by default this.updateAutomation(false); } Loading @@ -192,7 +233,12 @@ class ConfigPane extends Component { } setScenario(name) { if (!name || name === this.props.networkFileSelected) { return; } log.info(`setScenario: Setting new scenario network: ${name}`); this.props.changeCurrentAlert(null); this.props.changeDialogErrorMsg(''); // Reset config & api panes this.resetConfigPane(); this.resetApiPane(); Loading Loading @@ -497,9 +543,12 @@ class ConfigPane extends Component { disabled={!networkFileSelectionEnabled} options={this.props.networkFiles} onChange={event => { if (this.ignoreNetworkSelectChange) { return; } this.setScenario(event.target.value); }} value={this.props.networkFileSelected} value={this.props.networkFileSelected || DEFAULT_NO_NETWORK_FILE_SELECTED} /> {this.props.currentAlert === ALERT_NEM_NOT_RUNNING && <Typography use="subtitle2" style={styles.nemErrorText}> Loading Loading @@ -771,6 +820,7 @@ const mapStateToProps = state => { return { page: state.ui.page, currentAlert: state.ui.currentAlert, dialogErrorMsg: state.ui.dialogErrorMsg, networkFiles: state.ui.networkFiles, networkFileSelected: state.ui.networkFileSelected, networkInfo: state.ui.networkInfo, Loading @@ -794,6 +844,8 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { changeCurrentDialog: type => dispatch(uiChangeCurrentDialog(type)), changeCurrentAlert: alert => dispatch(uiChangeCurrentAlert(alert)), changeDialogErrorMsg: msg => dispatch(uiChangeDialogErrorMsg(msg)), changeTerminationInProgressCount: count => dispatch(uiSandboxChangeTerminationInProgressCount(count)), changeActivationInProgressCount: count => dispatch(uiSandboxChangeActivationInProgressCount(count)), changeActivationInProgressScenarioName: name => dispatch(uiSandboxChangeActivationInProgressScenarioName(name)), Loading
js-apps/frontend/src/js/util/nem-error.js 0 → 100644 +63 −0 Changes for js-apps/frontend/src/js/util/nem-error.js: 63 added lines, 0 removed lines. Original line number Diff line number Diff line import { StatusCodes } from 'http-status-codes'; import { MSG_NEM_NOT_RUNNING } from '../app-constants'; /** * HTTP status from a SuperAgent error or response. * @param {Object} error * @param {Object} response * @return {number|undefined} */ export function nemHttpStatus(error, response) { if (error && typeof error.status === 'number') { return error.status; } if (response && typeof response.status === 'number') { return response.status; } return undefined; } /** * Best-effort text of an HTTP error body. Empty when SuperAgent did not * expose a body (do not invent a "failed to start" fallback). * @param {Object} error * @param {Object} response * @return {string} */ export function nemErrorText(error, response) { const candidates = [ response && response.text, response && response.body && response.body.error, response && typeof response.body === 'string' && response.body, error && error.response && error.response.text, error && error.response && error.response.body ]; for (let i = 0; i < candidates.length; i++) { const v = candidates[i]; if (typeof v === 'string' && v.trim()) { return v.trim(); } } return ''; } /** * True when NEM HTTP was not reached. Activate application errors (NEM 500, * sudo, INACTIVE) must not match, even if the controller wrapped them as 502. * @param {Object} error * @param {Object} response * @return {boolean} */ export function isNemUnreachable(error, response) { const status = nemHttpStatus(error, response); if (status === StatusCodes.GATEWAY_TIMEOUT || status === StatusCodes.REQUEST_TIMEOUT || status === 0) { return true; } if (status === StatusCodes.BAD_GATEWAY) { const text = nemErrorText(error, response); return text === MSG_NEM_NOT_RUNNING || text.indexOf(MSG_NEM_NOT_RUNNING) !== -1; } return false; }